[ICD] Update ICDM 2.1 Certification test to match test plan (#33404)
* WIP
* Update icdm 2.1 cert test
* Update ci PICS
* Fix ICDM test script
* Added unit tests for ICDM 2.1 test script
* Fix python linter
* fix ci
* move staticmethods inside the class
* Fix ci for missing function
* Fix bit count function
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index 0c0290f..58bdd23 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -560,6 +560,7 @@
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-rvc-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-rvc-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace_file json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_RVCOPSTATE_2_3.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --PICS examples/rvc-app/rvc-common/pics/rvc-app-pics-values --endpoint 1 --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"'
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-rvc-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-rvc-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace_file json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_RVCOPSTATE_2_4.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --PICS examples/rvc-app/rvc-common/pics/rvc-app-pics-values --endpoint 1 --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"'
scripts/run_in_python_env.sh out/venv './src/python_testing/test_testing/test_TC_DA_1_2.py'
+ scripts/run_in_python_env.sh out/venv './src/python_testing/test_testing/test_TC_ICDM_2_1.py'
- name: Uploading core files
uses: actions/upload-artifact@v4
if: ${{ failure() && !env.ACT }}
diff --git a/examples/lit-icd-app/lit-icd-common/lit-icd-server-app.matter b/examples/lit-icd-app/lit-icd-common/lit-icd-server-app.matter
index cf3d071..5274f24 100644
--- a/examples/lit-icd-app/lit-icd-common/lit-icd-server-app.matter
+++ b/examples/lit-icd-app/lit-icd-common/lit-icd-server-app.matter
@@ -1783,7 +1783,7 @@
callback attribute registeredClients;
callback attribute ICDCounter;
callback attribute clientsSupportedPerFabric;
- ram attribute userActiveModeTriggerHint default = 0x110D;
+ ram attribute userActiveModeTriggerHint default = 0x111D;
ram attribute userActiveModeTriggerInstruction default = "Restart the application";
ram attribute operatingMode default = 0;
callback attribute generatedCommandList;
diff --git a/examples/lit-icd-app/lit-icd-common/lit-icd-server-app.zap b/examples/lit-icd-app/lit-icd-common/lit-icd-server-app.zap
index 7780ccc..6bbfc90 100644
--- a/examples/lit-icd-app/lit-icd-common/lit-icd-server-app.zap
+++ b/examples/lit-icd-app/lit-icd-common/lit-icd-server-app.zap
@@ -1,6 +1,6 @@
{
"fileFormat": 2,
- "featureLevel": 100,
+ "featureLevel": 102,
"creator": "zap",
"keyValuePairs": [
{
@@ -29,6 +29,7 @@
"pathRelativity": "relativeToZap",
"path": "../../../src/app/zap-templates/app-templates.json",
"type": "gen-templates-json",
+ "category": "matter",
"version": "chip-v1"
}
],
@@ -3497,7 +3498,7 @@
"storageOption": "RAM",
"singleton": 0,
"bounded": 0,
- "defaultValue": "0x110D",
+ "defaultValue": "0x111D",
"reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
@@ -3699,7 +3700,7 @@
"singleton": 0,
"bounded": 0,
"defaultValue": "0x0",
- "reportable": 0,
+ "reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
"reportableChange": 0
@@ -3715,7 +3716,7 @@
"singleton": 0,
"bounded": 0,
"defaultValue": "0x00",
- "reportable": 0,
+ "reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
"reportableChange": 0
@@ -3730,7 +3731,7 @@
"storageOption": "External",
"singleton": 0,
"bounded": 0,
- "defaultValue": "",
+ "defaultValue": null,
"reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
@@ -3746,7 +3747,7 @@
"storageOption": "External",
"singleton": 0,
"bounded": 0,
- "defaultValue": "",
+ "defaultValue": null,
"reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
@@ -3762,7 +3763,7 @@
"storageOption": "External",
"singleton": 0,
"bounded": 0,
- "defaultValue": "",
+ "defaultValue": null,
"reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
@@ -3778,7 +3779,7 @@
"storageOption": "External",
"singleton": 0,
"bounded": 0,
- "defaultValue": "",
+ "defaultValue": null,
"reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
@@ -3836,7 +3837,7 @@
"storageOption": "External",
"singleton": 0,
"bounded": 0,
- "defaultValue": "",
+ "defaultValue": null,
"reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
@@ -3852,8 +3853,8 @@
"storageOption": "External",
"singleton": 0,
"bounded": 0,
- "defaultValue": "",
- "reportable": 0,
+ "defaultValue": null,
+ "reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
"reportableChange": 0
@@ -3868,8 +3869,8 @@
"storageOption": "External",
"singleton": 0,
"bounded": 0,
- "defaultValue": "",
- "reportable": 0,
+ "defaultValue": null,
+ "reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
"reportableChange": 0
@@ -3884,8 +3885,8 @@
"storageOption": "External",
"singleton": 0,
"bounded": 0,
- "defaultValue": "",
- "reportable": 0,
+ "defaultValue": null,
+ "reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
"reportableChange": 0
@@ -3900,7 +3901,7 @@
"storageOption": "External",
"singleton": 0,
"bounded": 0,
- "defaultValue": "",
+ "defaultValue": null,
"reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
@@ -3916,7 +3917,7 @@
"storageOption": "External",
"singleton": 0,
"bounded": 0,
- "defaultValue": "",
+ "defaultValue": null,
"reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
@@ -3932,7 +3933,7 @@
"storageOption": "External",
"singleton": 0,
"bounded": 0,
- "defaultValue": "",
+ "defaultValue": null,
"reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
@@ -3948,7 +3949,7 @@
"storageOption": "External",
"singleton": 0,
"bounded": 0,
- "defaultValue": "",
+ "defaultValue": null,
"reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
@@ -3964,7 +3965,7 @@
"storageOption": "External",
"singleton": 0,
"bounded": 0,
- "defaultValue": "0",
+ "defaultValue": null,
"reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
@@ -3980,7 +3981,7 @@
"storageOption": "External",
"singleton": 0,
"bounded": 0,
- "defaultValue": "2",
+ "defaultValue": null,
"reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
@@ -4022,7 +4023,7 @@
"storageOption": "External",
"singleton": 0,
"bounded": 0,
- "defaultValue": "",
+ "defaultValue": null,
"reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
@@ -4038,7 +4039,7 @@
"storageOption": "External",
"singleton": 0,
"bounded": 0,
- "defaultValue": "",
+ "defaultValue": null,
"reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
@@ -4054,7 +4055,7 @@
"storageOption": "External",
"singleton": 0,
"bounded": 0,
- "defaultValue": "",
+ "defaultValue": null,
"reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
@@ -4070,7 +4071,7 @@
"storageOption": "External",
"singleton": 0,
"bounded": 0,
- "defaultValue": "",
+ "defaultValue": null,
"reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
diff --git a/src/app/tests/suites/TestIcdManagementCluster.yaml b/src/app/tests/suites/TestIcdManagementCluster.yaml
index f7f207a..a319683 100644
--- a/src/app/tests/suites/TestIcdManagementCluster.yaml
+++ b/src/app/tests/suites/TestIcdManagementCluster.yaml
@@ -141,7 +141,7 @@
response:
constraints:
type: bitmap32
- value: 0x110D
+ value: 0x111D
- label: "Read UserActiveModeTriggerInstruction"
command: "readAttribute"
diff --git a/src/app/tests/suites/certification/PICS.yaml b/src/app/tests/suites/certification/PICS.yaml
index d23a1db..cf01598 100644
--- a/src/app/tests/suites/certification/PICS.yaml
+++ b/src/app/tests/suites/certification/PICS.yaml
@@ -8692,10 +8692,13 @@
"Does the device implement the UserActiveModeTriggerHint attribute?"
id: ICDM.S.A0006
+ - label: "Does the device implement the OperatingMode? attribute?"
+ id: ICDM.S.A0007
+
- label:
"Does the device implement the UserActiveModeTriggerInstruction
attribute?"
- id: ICDM.S.A0007
+ id: ICDM.S.A0008
#
# Client Attribute
diff --git a/src/app/tests/suites/certification/ci-pics-values b/src/app/tests/suites/certification/ci-pics-values
index 20f0a33..aa8fb52 100644
--- a/src/app/tests/suites/certification/ci-pics-values
+++ b/src/app/tests/suites/certification/ci-pics-values
@@ -2668,6 +2668,7 @@
ICDM.S.A0005=1
ICDM.S.A0006=1
ICDM.S.A0007=1
+ICDM.S.A0008=1
#Client Attribute
ICDM.C.A0000=1
diff --git a/src/python_testing/TC_ICDM_2_1.py b/src/python_testing/TC_ICDM_2_1.py
index 21a3082..6af092a 100644
--- a/src/python_testing/TC_ICDM_2_1.py
+++ b/src/python_testing/TC_ICDM_2_1.py
@@ -15,98 +15,250 @@
# limitations under the License.
#
import logging
+import re
import chip.clusters as Clusters
-from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main
+from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main
from mobly import asserts
-logger = logging.getLogger('PythonMatterControllerTEST')
-logger.setLevel(logging.INFO)
+logger = logging.getLogger(__name__)
+
+kRootEndpointId = 0
+kMaxUserActiveModeBitmap = 0x1FFFF
+kMaxUserActiveModeTriggerInstructionByteLength = 128
+
+cluster = Clusters.Objects.IcdManagement
+uat = cluster.Bitmaps.UserActiveModeTriggerBitmap
+modes = cluster.Enums.OperatingModeEnum
+features = cluster.Bitmaps.Feature
+
+# BitMask for all user active mode trigger hints that are depedent on the UserActiveModeTriggerInstruction
+kUatInstructionDependentBitMask = uat.kCustomInstruction | uat.kActuateSensorSeconds | uat.kActuateSensorTimes | uat.kActuateSensorLightsBlink | uat.kResetButtonLightsBlink | uat.kResetButtonSeconds | uat.kResetButtonTimes | uat.kSetupButtonSeconds | uat.kSetupButtonLightsBlink | uat.kSetupButtonTimes | uat.kAppDefinedButton
+
+# BitMask for UserActiveModeTriggerHint that REQUIRE the prescense of the UserActiveModeTriggerInstruction
+kUatInstructionMandatoryBitMask = uat.kCustomInstruction | uat.kActuateSensorSeconds | uat.kActuateSensorTimes | uat.kResetButtonSeconds | uat.kResetButtonTimes | uat.kSetupButtonSeconds | uat.kSetupButtonTimes | uat.kAppDefinedButton
+
+# BitMask for all user active mode trigger hints that have the UserActiveModeTriggerInstruction as an uint
+kUatNumberInstructionBitMask = uat.kActuateSensorSeconds | uat.kActuateSensorTimes | uat.kResetButtonSeconds | uat.kResetButtonTimes | uat.kSetupButtonSeconds | uat.kSetupButtonTimes
+
+# BitMask for all user active mode trigger hints that provide a color in the UserActiveModeTriggerInstruction
+kUatColorInstructionBitMask = uat.kActuateSensorLightsBlink | uat.kResetButtonLightsBlink | uat.kSetupButtonLightsBlink
class TC_ICDM_2_1(MatterBaseTest):
- async def read_icdm_attribute_expect_success(self, endpoint, attribute):
- cluster = Clusters.Objects.IcdManagement
- return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=attribute)
+
+ #
+ # Class Helper functions
+ #
+
+ @staticmethod
+ def is_valid_uint32_value(var):
+ return isinstance(var, int) and 0 <= var <= 0xFFFFFFFF
+
+ @staticmethod
+ def is_valid_uint16_value(var):
+ return isinstance(var, int) and 0 <= var <= 0xFFFF
+
+ @staticmethod
+ def is_valid_uint8_value(var):
+ return isinstance(var, int) and 0 <= var <= 0xFF
+
+ @staticmethod
+ def set_bits_count(number):
+ return bin(number).count("1")
+
+ async def _read_icdm_attribute_expect_success(self, attribute):
+ return await self.read_single_attribute_check_success(endpoint=kRootEndpointId, cluster=cluster, attribute=attribute)
+
+ async def _wildcard_cluster_read(self):
+ return await self.default_controller.ReadAttribute(self.dut_node_id, [(kRootEndpointId, cluster)])
+
+ #
+ # Test Harness Helpers
+ #
+
+ def desc_TC_ICDM_2_1(self) -> str:
+ """Returns a description of this test"""
+ return "[TC_ICDM_2_1] attributes with DUT as Server"
+
+ def steps_TC_ICDM_2_1(self) -> list[TestStep]:
+ steps = [
+ TestStep(1, "Commissioning, already done", is_commissioning=True),
+ TestStep(2, "TH reads from the DUT the ActiveModeThreshold attribute."),
+ TestStep(3, "TH reads from the DUT the ActiveModeDuration attribute."),
+ TestStep(4, "TH reads from the DUT the IdleModeDuration attribute."),
+ TestStep(
+ 5, "TH reads from the DUT the ClientsSupportedPerFabric attribute."),
+ TestStep(6, "TH reads from the DUT the RegisteredClients attribute."),
+ TestStep(7, "TH reads from the DUT the ICDCounter attribute."),
+ TestStep(
+ 8, "TH reads from the DUT the UserActiveModeTriggerHint attribute."),
+ TestStep(
+ 9, "TH reads from the DUT the UserActiveModeTriggerInstruction attribute"),
+ TestStep(10, "TH reads from the DUT the OperatingMode attribute."),
+ ]
+ return steps
def pics_TC_ICDM_2_1(self) -> list[str]:
- return ["ICDM.S"]
+ """ This function returns a list of PICS for this test case that must be True for the test to be run"""
+ pics = [
+ "ICDM.S",
+ ]
+ return pics
+
+ #
+ # ICDM 2.1 Test Body
+ #
@async_test_body
async def test_TC_ICDM_2_1(self):
- if not self.check_pics("ICDM.S"):
- logger.info("Test skipped because PICS ICDM.S is not set")
- return
+ cluster = Clusters.Objects.IcdManagement
+ attributes = cluster.Attributes
- endpoint = self.user_params.get("endpoint", 0)
+ # Commissioning
+ self.step(1)
+ # Read feature map
+ featureMap = await self._read_icdm_attribute_expect_success(
+ attributes.FeatureMap)
- self.print_step(1, "Commissioning, already done")
- attributes = Clusters.IcdManagement.Attributes
- idleModeDuration = 0
+ # Validate ActiveModeThreshold
+ self.step(2)
+ if self.check_pics("ICDM.S.A0002"):
- # Idle Mode Duration attribute test
- if (self.check_pics("ICDM.S.A0000")):
- self.print_step(2, "Read IdleModeDuration Attribute")
-
- idleModeDuration = await self.read_icdm_attribute_expect_success(endpoint=endpoint,
- attribute=attributes.IdleModeDuration)
- asserts.assert_greater_equal(idleModeDuration, 1, "IdleModeDuration attribute is smaller than minimum value (1).")
- asserts.assert_less_equal(idleModeDuration, 64800, "IdleModeDuration attribute is greater than maximum value (64800).")
- else:
- asserts.assert_true(False, "IdleModeDuration is a mandatory attribute and must be present in the PICS file")
-
- # Active Mode Duration attribute test
- if (self.check_pics("ICDM.S.A0001")):
- self.print_step(2, "Read ActiveModeDuration Attribute")
-
- idleModeDuration *= 1000 # Convert seconds to milliseconds
- activeModeDuration = await self.read_icdm_attribute_expect_success(endpoint=endpoint,
- attribute=attributes.ActiveModeDuration)
- asserts.assert_true(0 <= activeModeDuration <= 65535,
- "ActiveModeDuration attribute does not fit in a uint16.")
- asserts.assert_less_equal(activeModeDuration, idleModeDuration,
- "ActiveModeDuration attribute is greater than the IdleModeDuration attrbiute.")
- else:
- asserts.assert_true(False, "ActiveModeDuration is a mandatory attribute and must be present in the PICS file")
-
- # Active Mode Threshold attribute test
- if (self.check_pics("ICDM.S.A0002")):
- self.print_step(2, "Read ActiveModeThreshold Attribute")
-
- activeModeThreshold = await self.read_icdm_attribute_expect_success(endpoint=endpoint,
- attribute=attributes.ActiveModeThreshold)
- asserts.assert_true(0 <= activeModeThreshold <= 65535,
+ activeModeThreshold = await self._read_icdm_attribute_expect_success(
+ attributes.ActiveModeThreshold)
+ # Verify ActiveModeThreshold is not bigger than uint16
+ asserts.assert_true(self.is_valid_uint16_value(activeModeThreshold),
"ActiveModeThreshold attribute does not fit in a uint16.")
+
+ if featureMap > 0 and features.kLongIdleTimeSupport in features(featureMap):
+ asserts.assert_greater_equal(
+ activeModeThreshold, 5000, "Minimum ActiveModeThreshold is 5s for a LIT ICD.")
+
else:
- asserts.assert_true(False, "ActiveModeThreshold is a mandatory attribute and must be present in the PICS file")
+ asserts.assert_true(
+ False, "ActiveModeThreshold is a mandatory attribute and must be present in the PICS file")
- # RegisteredClients attribute test
- if (self.check_pics("ICDM.S.A0003")):
- self.print_step(2, "Read RegisteredClients Attribute")
+ # Validate ActiveModeDuration
+ self.step(3)
+ if self.check_pics("ICDM.S.A0001"):
+ activeModeDuration = await self._read_icdm_attribute_expect_success(
+ attributes.ActiveModeDuration)
+ # Verify ActiveModeDuration is not bigger than uint32
+ asserts.assert_true(self.is_valid_uint32_value(activeModeDuration),
+ "ActiveModeDuration attribute does not fit in a uint32")
+ else:
+ asserts.assert_true(
+ False, "ActiveModeDuration is a mandatory attribute and must be present in the PICS file")
- await self.read_icdm_attribute_expect_success(endpoint=endpoint,
- attribute=attributes.RegisteredClients)
+ # Validate IdleModeDuration
+ self.step(4)
+ if self.check_pics("ICDM.S.A0000"):
+ idleModeDuration = await self._read_icdm_attribute_expect_success(
+ attributes.IdleModeDuration)
+ # Verify IdleModeDuration is not bigger than uint32
+ asserts.assert_greater_equal(
+ idleModeDuration, 1, "IdleModeDuration attribute is smaller than minimum value (1).")
+ asserts.assert_less_equal(
+ idleModeDuration, 64800, "IdleModeDuration attribute is greater than maximum value (64800).")
+ asserts.assert_greater_equal(idleModeDuration * 1000, activeModeDuration,
+ "ActiveModeDuration attribute is greater than the IdleModeDuration attrbiute.")
+ else:
+ asserts.assert_true(
+ False, "IdleModeDuration is a mandatory attribute and must be present in the PICS file")
- # ICDCounter attribute test
- if (self.check_pics("ICDM.S.A0003")):
- self.print_step(2, "Read ICDCounter Attribute")
+ # Validate ClientsSupportedPerFabric
+ self.step(5)
+ if self.pics_guard(self.check_pics("ICDM.S.A0005")):
+ clientsSupportedPerFabric = await self._read_icdm_attribute_expect_success(
+ attributes.ClientsSupportedPerFabric)
- ICDCounter = await self.read_icdm_attribute_expect_success(endpoint=endpoint,
- attribute=attributes.ICDCounter)
- asserts.assert_true(0 <= ICDCounter <= 4294967295,
- "ICDCounter attribute does not fit in a uint32.")
+ # Verify ClientsSupportedPerFabric is not bigger than uint16
+ asserts.assert_true(self.is_valid_uint16_value(clientsSupportedPerFabric),
+ "ClientsSupportedPerFabric attribute does not fit in a uint16.")
- # ClientsSupportedPerFabric attribute test
- if (self.check_pics("ICDM.S.A0003")):
- self.print_step(2, "Read ClientsSupportedPerFabric Attribute")
+ asserts.assert_greater_equal(
+ clientsSupportedPerFabric, 1, "ClientsSupportedPerFabric attribute is smaller than minimum value (1).")
- clientsSupportedPerFabric = await self.read_icdm_attribute_expect_success(endpoint=endpoint,
- attribute=attributes.ClientsSupportedPerFabric)
- asserts.assert_true(0 <= clientsSupportedPerFabric <= 65535,
- "ActiveModeThreshold ClientsSupportedPerFabric does not fit in a uint16.")
- asserts.assert_greater_equal(clientsSupportedPerFabric, 1,
- "ClientsSupportedPerFabric attribute is smaller than minimum value (1).")
+ # Validate RegisteredClients
+ self.step(6)
+ if self.pics_guard(self.check_pics("ICDM.S.A0003")):
+ registeredClients = await self._read_icdm_attribute_expect_success(
+ attributes.RegisteredClients)
+
+ asserts.assert_true(isinstance(
+ registeredClients, list), "RegisteredClients is not a list.")
+
+ # Validate ICDCounter
+ self.step(7)
+ if self.pics_guard(self.check_pics("ICDM.S.A0004")):
+
+ icdCounter = await self._read_icdm_attribute_expect_success(
+ attributes.ICDCounter)
+ # Verify ICDCounter is not bigger than uint32
+ asserts.assert_true(self.is_valid_uint32_value(icdCounter),
+ "ActiveModeDuration attribute does not fit in a uint32")
+
+ # Validate UserActiveModeTriggerHint
+ self.step(8)
+ if self.pics_guard(self.check_pics("ICDM.S.A0006")):
+ userActiveModeTriggerHint = await self._read_icdm_attribute_expect_success(
+ attributes.UserActiveModeTriggerHint)
+
+ # Verify that it is a bitmap32 - Only the first 16 bits are used
+ asserts.assert_true(0 <= userActiveModeTriggerHint <= kMaxUserActiveModeBitmap,
+ "UserActiveModeTriggerHint attribute does not fit in a bitmap32")
+
+ # Verify that only a single UserActiveModeTriggerInstruction dependent bit is set
+ uatHintInstructionDepedentBitmap = uat(
+ userActiveModeTriggerHint) & kUatInstructionDependentBitMask
+
+ asserts.assert_less_equal(
+ self.set_bits_count(uatHintInstructionDepedentBitmap), 1, "UserActiveModeTriggerHint has more than 1 bit that is dependent on the UserActiveModeTriggerInstruction")
+
+ # Valdate UserActiveModeTriggerInstruction
+ self.step(9)
+ if self.check_pics("ICDM.S.A0007"):
+ userActiveModeTriggerInstruction = await self._read_icdm_attribute_expect_success(
+ attributes.UserActiveModeTriggerInstruction)
+
+ # Verify that the UserActiveModeTriggerInstruction has the correct encoding
+ try:
+ encodedUATInstruction = userActiveModeTriggerInstruction.encode(
+ 'utf-8')
+ except Exception:
+ asserts.assert_true(
+ False, "UserActiveModeTriggerInstruction is not encoded in the correct format (utf-8).")
+
+ # Verify byte length of the UserActiveModeTirggerInstruction
+ asserts.assert_less_equal(
+ len(encodedUATInstruction), kMaxUserActiveModeTriggerInstructionByteLength, "UserActiveModeTriggerInstruction is longuer than the maximum allowed length (128).")
+
+ if uatHintInstructionDepedentBitmap > 0 and uatHintInstructionDepedentBitmap in kUatNumberInstructionBitMask:
+ # Validate Instruction is a decimal unsigned integer using the ASCII digits 0-9, and without leading zeros.
+ asserts.assert_true((re.search(r'^(?!0)[0-9]*$', userActiveModeTriggerInstruction) is not None),
+ "UserActiveModeTriggerInstruction is not in the correct format for the associated UserActiveModeTriggerHint")
+
+ if uatHintInstructionDepedentBitmap > 0 and uatHintInstructionDepedentBitmap in kUatColorInstructionBitMask:
+ # TODO: https://github.com/CHIP-Specifications/connectedhomeip-spec/issues/9194
+ asserts.assert_true(False, "Nothing to do for now")
+ else:
+ # Check if the UserActiveModeTriggerInstruction was required
+ asserts.assert_false(uatHintInstructionDepedentBitmap in kUatInstructionMandatoryBitMask,
+ "UserActiveModeTriggerHint requires the UserActiveModeTriggerInstruction")
+
+ # Verify OperatingMode
+ self.step(10)
+ if self.pics_guard(self.check_pics("ICDM.S.A0008")):
+ operatingMode = await self._read_icdm_attribute_expect_success(
+ attributes.OperatingMode)
+
+ asserts.assert_true(self.is_valid_uint8_value(operatingMode),
+ "OperatingMode does not fit in an enum8")
+
+ asserts.assert_less(
+ operatingMode, modes.kUnknownEnumValue, "OperatingMode can only have 0 and 1 as valid values")
if __name__ == "__main__":
diff --git a/src/python_testing/test_testing/MockTestRunner.py b/src/python_testing/test_testing/MockTestRunner.py
index 451e38d..5d6592b 100644
--- a/src/python_testing/test_testing/MockTestRunner.py
+++ b/src/python_testing/test_testing/MockTestRunner.py
@@ -37,9 +37,9 @@
class MockTestRunner():
- def __init__(self, filename: str, classname: str, test: str, endpoint: int):
+ def __init__(self, filename: str, classname: str, test: str, endpoint: int, pics: dict[str, bool] = {}):
self.config = MatterTestConfig(
- tests=[test], endpoint=endpoint, dut_node_ids=[1])
+ tests=[test], endpoint=endpoint, dut_node_ids=[1], pics=pics)
self.stack = MatterStackState(self.config)
self.default_controller = self.stack.certificate_authorities[0].adminList[0].NewController(
nodeId=self.config.controller_node_id,
diff --git a/src/python_testing/test_testing/test_TC_ICDM_2_1.py b/src/python_testing/test_testing/test_TC_ICDM_2_1.py
new file mode 100755
index 0000000..5069eb6
--- /dev/null
+++ b/src/python_testing/test_testing/test_TC_ICDM_2_1.py
@@ -0,0 +1,235 @@
+#!/usr/bin/env -S python3 -B
+#
+# Copyright (c) 2024 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.
+#
+
+import string
+import sys
+from dataclasses import dataclass
+
+import chip.clusters as Clusters
+from chip.clusters import Attribute
+from MockTestRunner import MockTestRunner
+
+c = Clusters.IcdManagement
+attr = c.Attributes
+uat = c.Bitmaps.UserActiveModeTriggerBitmap
+
+
+@dataclass
+class ICDMData():
+ FeatureMap: int
+ IdleModeDuration: int
+ ActiveModeDuration: int
+ ActiveModeThreshold: int
+ RegisteredClients: list
+ ICDCounter: int
+ ClientsSupportedPerFabric: int
+ UserActiveModeTriggerHint: int
+ UserActiveModeTriggerInstruction: string
+ OperatingMode: c.Enums.OperatingModeEnum
+ expect_pass: bool
+
+
+long_string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut e"
+too_long_string = long_string + "1"
+
+TEST_CASES = [
+
+ # ==============================
+ # ICDM 2.1 Test cases
+ # ==============================
+ # --------
+ # Test cases to validate IdleModeDuration
+ # --------
+ # IdleModeDuration under minimum (< 1)
+ ICDMData(0, 0, 0, 100, [], 0, 2, 0, "",
+ c.Enums.OperatingModeEnum.kSit, False),
+ # IdleModeDuration at minimum
+ ICDMData(0, 1, 0, 100, [], 0, 2, 0, "",
+ c.Enums.OperatingModeEnum.kSit, True),
+ # IdleModeDuration at maximum
+ ICDMData(0, 64800, 100, 100, [], 0, 2, 0, "",
+ c.Enums.OperatingModeEnum.kSit, True),
+ # IdleModeDuration over maximum (>64800)
+ ICDMData(0, 64801, 100, 100, [], 0, 2, 0, "",
+ c.Enums.OperatingModeEnum.kSit, False),
+ # IdleModeDuration < ActiveModeDuration
+ ICDMData(0, 1, 1001, 100, [], 0, 2, 0, "",
+ c.Enums.OperatingModeEnum.kSit, False),
+ # --------
+ # Test cases to validate ActiveModeDuration
+ # --------
+ # ActiveModeDuration under minimum
+ ICDMData(0, 100, -1, 100, [], 0, 2, 0, "",
+ c.Enums.OperatingModeEnum.kSit, False),
+ # ActiveModeDuration at minimum
+ ICDMData(0, 100, 0, 100, [], 0, 2, 0, "",
+ c.Enums.OperatingModeEnum.kSit, True),
+ # ActiveModeDuration at maximum - value is max IdleModeDuration value - 1
+ ICDMData(0, 64800, 0x3DCC4FF, 100, [], 0, 2, 0, "",
+ c.Enums.OperatingModeEnum.kSit, True),
+ # --------
+ # Test cases to validate ActiveModeThreshold
+ # --------
+ # ActiveModeThreshold < minimum
+ ICDMData(0, 1, 0, -1, [], 0, 2, 0, "",
+ c.Enums.OperatingModeEnum.kSit, False),
+ # ActiveModeThreshold at SIT minimum
+ ICDMData(0, 1, 0, 0, [], 0, 2, 0, "",
+ c.Enums.OperatingModeEnum.kSit, True),
+ # ActiveModeThreshold under LIT minimum
+ ICDMData(0x7, 1, 0, 4999, [], 0, 2, 0, "",
+ c.Enums.OperatingModeEnum.kLit, False),
+ # ActiveModeThreshold at LIT minimum
+ ICDMData(0x7, 1, 0, 5000, [], 0, 2, 0, "",
+ c.Enums.OperatingModeEnum.kLit, True),
+ # ActiveModeThreshold at Maximum
+ ICDMData(0, 1, 0, 0xFFFF, [], 0, 2, 0, "",
+ c.Enums.OperatingModeEnum.kSit, True),
+ # ActiveModeThreshold over Maximum
+ ICDMData(0, 1, 0, 0x10000, [], 0, 2, 0, "",
+ c.Enums.OperatingModeEnum.kSit, False),
+ # --------
+ # Test cases to validate ClientsSupportedPerFabric
+ # --------
+ # ClientsSupportedPerFabric under minimum (< 1)
+ ICDMData(0, 1, 0, 100, [], 0, 0, 0, "",
+ c.Enums.OperatingModeEnum.kLit, False),
+ # ClientsSupportedPerFabric at minimum
+ ICDMData(0, 1, 0, 100, [], 0, 1, 0, "",
+ c.Enums.OperatingModeEnum.kLit, True),
+ # ClientsSupportedPerFabric at maximum
+ ICDMData(0, 1, 0, 100, [], 0, 255, 0, "",
+ c.Enums.OperatingModeEnum.kLit, True),
+ # ClientsSupportedPerFabric > maximum
+ ICDMData(0, 1, 0, 100, [], 0, 256, 0, "",
+ c.Enums.OperatingModeEnum.kLit, True),
+ # --------
+ # Test cases to validate RegisteredClients
+ # --------
+ # Incorrect type
+ ICDMData(0, 1, 0, 100, 0, 0, 1, 0, "",
+ c.Enums.OperatingModeEnum.kLit, False),
+ # Correct type
+ ICDMData(0, 1, 0, 100, [], 0, 1, 0, "",
+ c.Enums.OperatingModeEnum.kLit, True),
+ # --------
+ # Test cases to validate ICDCounter
+ # --------
+ # ICDCounter under minimum (< 0)
+ ICDMData(0, 1, 0, 100, [], -1, 1, 0, "",
+ c.Enums.OperatingModeEnum.kLit, False),
+ # ICDCounter at minimum
+ ICDMData(0, 1, 0, 100, [], 0, 1, 0, "",
+ c.Enums.OperatingModeEnum.kLit, True),
+ # ICDCounter at maximum
+ ICDMData(0, 1, 0, 100, [], 0xFFFFFFFF, 1, 0, "",
+ c.Enums.OperatingModeEnum.kLit, True),
+ # ICDCounter over maximum
+ ICDMData(0, 1, 0, 100, [], 0x100000000, 1, 0, "",
+ c.Enums.OperatingModeEnum.kLit, False),
+ # --------
+ # Test cases to validate UserActiveModeTriggerHint
+ # --------
+ # UserActiveModeTriggerHint outsite valid range
+ ICDMData(0, 1, 0, 100, [], 0, 1, 0x1FFFF, "",
+ c.Enums.OperatingModeEnum.kLit, False),
+ # UserActiveModeTriggerHint outsite valid range
+ ICDMData(0, 1, 0, 100, [], 0, 1, -1, "",
+ c.Enums.OperatingModeEnum.kLit, False),
+ # UserActiveModeTriggerHint with no hints
+ ICDMData(0, 1, 0, 100, [], 0, 1, 0, "",
+ c.Enums.OperatingModeEnum.kLit, True),
+ # UserActiveModeTriggerHint wiht two instruction depedent bits set
+ ICDMData(0, 1, 0, 100, [], 0, 1, uat.kCustomInstruction | uat.kActuateSensorSeconds, "",
+ c.Enums.OperatingModeEnum.kLit, False),
+ # --------
+ # Test cases to validate UserActiveModeTriggerInstruction
+ # --------
+ # UserActiveModeTriggerInstruction with wrong encoding
+ ICDMData(0, 1, 0, 100, [], 0, 1, uat.kCustomInstruction, "Hello\uD83D\uDE00World",
+ c.Enums.OperatingModeEnum.kLit, False),
+ # UserActiveModeTriggerInstruction with empty string
+ ICDMData(0, 1, 0, 100, [], 0, 1, uat.kCustomInstruction, "",
+ c.Enums.OperatingModeEnum.kLit, True),
+ # UserActiveModeTriggerInstruction with empty string
+ ICDMData(0, 1, 0, 100, [], 0, 1, uat.kCustomInstruction, "",
+ c.Enums.OperatingModeEnum.kLit, True),
+ # UserActiveModeTriggerInstruction with max string length
+ ICDMData(0, 1, 0, 100, [], 0, 1, uat.kCustomInstruction, long_string,
+ c.Enums.OperatingModeEnum.kLit, True),
+ # UserActiveModeTriggerInstruction > max string length
+ ICDMData(0, 1, 0, 100, [], 0, 1, uat.kCustomInstruction, too_long_string,
+ c.Enums.OperatingModeEnum.kLit, False),
+ # UserActiveModeTriggerInstruction invalid number - Trailing 0s
+ ICDMData(0, 1, 0, 100, [], 0, 1, uat.kActuateSensorSeconds, "001",
+ c.Enums.OperatingModeEnum.kLit, False),
+ # UserActiveModeTriggerInstruction invalid number - Letters
+ ICDMData(0, 1, 0, 100, [], 0, 1, uat.kActuateSensorSeconds, "not a number",
+ c.Enums.OperatingModeEnum.kLit, False),
+ # UserActiveModeTriggerInstruction Valid number
+ ICDMData(0, 1, 0, 100, [], 0, 1, uat.kActuateSensorSeconds, "100000",
+ c.Enums.OperatingModeEnum.kLit, True),
+ # --------
+ # Test cases to validate OpertingMode
+ # --------
+ # OpertingMode with negative value
+ ICDMData(0, 1, 0, 100, [], 0, 1, uat.kActuateSensorSeconds, "100000",
+ -1, False),
+ # OpertingMode with Accepted value
+ ICDMData(0, 1, 0, 100, [], 0, 1, uat.kActuateSensorSeconds, "100000",
+ c.Enums.OperatingModeEnum.kLit, True),
+ # OpertingMode with unkown value
+ ICDMData(0, 1, 0, 100, [], 0, 1, uat.kActuateSensorSeconds, "100000",
+ c.Enums.OperatingModeEnum.kUnknownEnumValue, False),
+
+]
+
+
+def test_spec_to_attribute_cache(test_icdm: ICDMData) -> Attribute.AsyncReadTransaction.ReadResponse:
+ resp = Attribute.AsyncReadTransaction.ReadResponse({}, [], {})
+ resp.attributes = {0: {c: {attr.FeatureMap: test_icdm.FeatureMap, attr.IdleModeDuration: test_icdm.IdleModeDuration, attr.ActiveModeDuration: test_icdm.ActiveModeDuration, attr.ActiveModeThreshold: test_icdm.ActiveModeThreshold,
+ attr.RegisteredClients: test_icdm.RegisteredClients, attr.ICDCounter: test_icdm.ICDCounter,
+ attr.ClientsSupportedPerFabric: test_icdm.ClientsSupportedPerFabric, attr.UserActiveModeTriggerHint: test_icdm.UserActiveModeTriggerHint,
+ attr.UserActiveModeTriggerInstruction: test_icdm.UserActiveModeTriggerInstruction, attr.OperatingMode: test_icdm.OperatingMode}}}
+ return resp
+
+
+def main():
+ pics = {"ICDM.S.A0000": True, "ICDM.S.A0001": True, "ICDM.S.A0002": True, "ICDM.S.A0003": True, "ICDM.S.A0004": True,
+ "ICDM.S.A0005": True, "ICDM.S.A0006": True, "ICDM.S.A0007": True, "ICDM.S.A0008": True, }
+
+ test_runner = MockTestRunner(
+ 'TC_ICDM_2_1', 'TC_ICDM_2_1', 'test_TC_ICDM_2_1', 0, pics)
+ failures = []
+ for idx, t in enumerate(TEST_CASES):
+ ok = test_runner.run_test_with_mock_read(
+ test_spec_to_attribute_cache(t)) == t.expect_pass
+ if not ok:
+ failures.append(f"Measured test case failure: {idx} {t}")
+
+ test_runner.Shutdown()
+ print(
+ f"Test of tests: run {len(TEST_CASES)}, test response correct: {len(TEST_CASES) - len(failures)} | test response incorrect: {len(failures)}")
+ for f in failures:
+ print(f)
+
+ return 1 if failures else 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())