From be581ef5d8df13bbdfd541488eb704425188e163 Mon Sep 17 00:00:00 2001 From: mkardous-silabs <84793247+mkardous-silabs@users.noreply.github.com> Date: Wed, 15 May 2024 20:39:25 -0400 Subject: [PATCH] [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 --- .github/workflows/tests.yaml | 1 + .../lit-icd-common/lit-icd-server-app.matter | 2 +- .../lit-icd-common/lit-icd-server-app.zap | 51 +-- .../suites/TestIcdManagementCluster.yaml | 2 +- src/app/tests/suites/certification/PICS.yaml | 5 +- .../tests/suites/certification/ci-pics-values | 1 + src/python_testing/TC_ICDM_2_1.py | 292 +++++++++++++----- .../test_testing/MockTestRunner.py | 4 +- .../test_testing/test_TC_ICDM_2_1.py | 235 ++++++++++++++ 9 files changed, 493 insertions(+), 100 deletions(-) create mode 100755 src/python_testing/test_testing/test_TC_ICDM_2_1.py diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 0c0290ff193230..58bdd2314c823a 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -560,6 +560,7 @@ jobs: 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 cf3d0710089d2c..5274f249f7628d 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 @@ endpoint 0 { 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 7780cccbac0c77..6bbfc9050a8365 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 f7f207ab8df310..a3196835915b52 100644 --- a/src/app/tests/suites/TestIcdManagementCluster.yaml +++ b/src/app/tests/suites/TestIcdManagementCluster.yaml @@ -141,7 +141,7 @@ tests: 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 d23a1db8cdc6cb..cf0159804aeee3 100644 --- a/src/app/tests/suites/certification/PICS.yaml +++ b/src/app/tests/suites/certification/PICS.yaml @@ -8692,10 +8692,13 @@ PICS: "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 20f0a334979c40..aa8fb52d289d2b 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.A0004=1 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 21a3082ef282a9..6af092a4ed5116 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 + + # Commissioning + self.step(1) + # Read feature map + featureMap = await self._read_icdm_attribute_expect_success( + attributes.FeatureMap) + + # Validate ActiveModeThreshold + self.step(2) + if self.check_pics("ICDM.S.A0002"): - endpoint = self.user_params.get("endpoint", 0) + 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.") - self.print_step(1, "Commissioning, already done") - attributes = Clusters.IcdManagement.Attributes - idleModeDuration = 0 + if featureMap > 0 and features.kLongIdleTimeSupport in features(featureMap): + asserts.assert_greater_equal( + activeModeThreshold, 5000, "Minimum ActiveModeThreshold is 5s for a LIT ICD.") - # Idle Mode Duration attribute test - if (self.check_pics("ICDM.S.A0000")): - self.print_step(2, "Read IdleModeDuration Attribute") + else: + asserts.assert_true( + False, "ActiveModeThreshold is a mandatory attribute and must be present in the PICS file") - 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).") + # 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, "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.") + asserts.assert_true( + False, "ActiveModeDuration is a mandatory attribute and must be present in the PICS file") + + # 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, "ActiveModeDuration is a mandatory attribute and must be present in the PICS file") + asserts.assert_true( + False, "IdleModeDuration 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") + # 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) - activeModeThreshold = await self.read_icdm_attribute_expect_success(endpoint=endpoint, - attribute=attributes.ActiveModeThreshold) - asserts.assert_true(0 <= activeModeThreshold <= 65535, - "ActiveModeThreshold attribute does not fit in a uint16.") + # Verify ClientsSupportedPerFabric is not bigger than uint16 + asserts.assert_true(self.is_valid_uint16_value(clientsSupportedPerFabric), + "ClientsSupportedPerFabric attribute 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: - 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") - - await self.read_icdm_attribute_expect_success(endpoint=endpoint, - attribute=attributes.RegisteredClients) - - # ICDCounter attribute test - if (self.check_pics("ICDM.S.A0003")): - self.print_step(2, "Read ICDCounter Attribute") - - 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.") - - # ClientsSupportedPerFabric attribute test - if (self.check_pics("ICDM.S.A0003")): - self.print_step(2, "Read ClientsSupportedPerFabric Attribute") - - 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).") + # 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 451e38d4660ac6..5d6592b5a8cd41 100644 --- a/src/python_testing/test_testing/MockTestRunner.py +++ b/src/python_testing/test_testing/MockTestRunner.py @@ -37,9 +37,9 @@ async def __call__(self, *args, **kwargs): 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 00000000000000..5069eb6ed9d0a7 --- /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())