diff --git a/src/python_testing/TC_SEAR_1_2.py b/src/python_testing/TC_SEAR_1_2.py new file mode 100644 index 00000000000000..4ebb3342ee9bfc --- /dev/null +++ b/src/python_testing/TC_SEAR_1_2.py @@ -0,0 +1,368 @@ +# +# 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. +# + +# TODO - this was copied/pasted from another test, it needs to be reviewed and updated +# 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 +# test-runner-run/run1/app: ${CHIP_RVC_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/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:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + +import logging +from time import sleep + +import chip.clusters as Clusters +from chip.clusters.Types import NullValue +from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main +from mobly import asserts + + +class TC_SEAR_1_2(MatterBaseTest): + def __init__(self, *args): + super().__init__(*args) + self.endpoint = None + self.is_ci = False + self.app_pipe = "/tmp/chip_rvc_fifo_" + self.mapid_list = [] + + # this must be kept in sync with the definitions from the Common Landmark Semantic Tag Namespace + self.MAX_LANDMARK_ID = 0x33 + + # this must be kept in sync with the definitions from the Common Relative Position Semantic Tag Namespace + self.MAX_RELPOS_ID = 0x07 + + async def read_sear_attribute_expect_success(self, endpoint, attribute): + cluster = Clusters.Objects.ServiceArea + return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=attribute) + + async def read_and_validate_supported_maps(self, step): + self.print_step(step, "Read SupportedMaps attribute") + supported_maps = await self.read_sear_attribute_expect_success( + endpoint=self.endpoint, attribute=Clusters.ServiceArea.Attributes.SupportedMaps) + logging.info("SupportedMaps: %s" % (supported_maps)) + asserts.assert_less_equal(len(supported_maps), 255, + "SupportedMaps should have max 255 entries") + + mapid_list = [m.mapID for m in supported_maps] + asserts.assert_true(len(set(mapid_list)) == len(mapid_list), "SupportedMaps must have unique MapID values!") + + name_list = [m.name for m in supported_maps] + asserts.assert_true(len(set(name_list)) == len(name_list), "SupportedMaps must have unique Name values!") + + # save so other methods can use this if neeeded + self.mapid_list = mapid_list + + async def read_and_validate_supported_areas(self, step): + self.print_step(step, "Read SupportedAreas attribute") + supported_areas = await self.read_sear_attribute_expect_success( + endpoint=self.endpoint, attribute=Clusters.ServiceArea.Attributes.SupportedAreas) + logging.info("SupportedAreas: %s" % (supported_areas)) + asserts.assert_less_equal(len(supported_areas), 255, + "SupportedAreas should have max 255 entries") + areaid_list = [] + areadesc_s = set() + for a in supported_areas: + asserts.assert_true(a.areaID not in areaid_list, "SupportedAreas must have unique AreaID values!") + + areaid_list.append(a.areaID) + + if len(self.mapid_list) > 0: + asserts.assert_is_not(a.mapID, NullValue, + f"SupportedAreas entry with AreaID({a.areaID}) should not have null MapID") + asserts.assert_is(a.mapID in self.mapid_list, + f"SupportedAreas entry with AreaID({a.areaID}) has unknown MapID({a.mapID})") + k = f"mapID:{a.mapID} areaDesc:{a.areaDesc}" + asserts.assert_true(k not in areadesc_s, + f"SupportedAreas must have unique MapID({a.mapID}) + AreaDesc({a.areaDesc}) values!") + areadesc_s.add(k) + else: + # empty SupportedMaps + asserts.assert_is(a.mapID, NullValue, + f"SupportedAreas entry with AreaID({a.areaID}) should have null MapID") + k = f"areaDesc:{a.areaDesc}" + asserts.assert_true(k not in areadesc_s, f"SupportedAreas must have unique AreaDesc({a.areaDesc}) values!") + areadesc_s.add(k) + + if a.locationInfo is NullValue and a.landmarkTag is NullValue: + asserts.assert_true( + f"SupportedAreas entry with AreaID({a.areaID}) should not have null LocationInfo and null LandmarkTag") + if a.landmarkTag is not NullValue: + asserts.assert_true(a.landmarkTag <= self.MAX_LANDMARK_ID, + f"SupportedAreas entry with AreaID({a.areaID}) has invalid LandmarkTag({a.landmarkTag})") + asserts.assert_true(a.positionTag is NullValue or a.positionTag in range(0, self.MAX_RELPOS_ID), + f"SupportedAreas entry with AreaID({a.areaID}) has invalid PositionTag({a.positionTag})") + # save so other methods can use this if neeeded + self.areaid_list = areaid_list + + async def read_and_validate_selected_areas(self, step): + self.print_step(step, "Read SelectedAreas attribute") + selected_areas = await self.read_sear_attribute_expect_success( + endpoint=self.endpoint, attribute=Clusters.ServiceArea.Attributes.SelectedAreas) + logging.info(f"SelectedAreas {selected_areas}") + + # TODO how to check if all entries are uint32? + + asserts.assert_true(len(selected_areas) <= len(self.areaid_list), + f"SelectedAreas(len {len(selected_areas)}) should have at most {len(self.areaid_list)} entries") + + asserts.assert_true(len(set(selected_areas)) == len(selected_areas), "SelectedAreas must have unique AreaID values!") + + for a in selected_areas: + asserts.assert_true(a in self.areaid_list, + f"SelectedAreas entry {a} has invalid value") + # save so other methods can use this if neeeded + self.selareaid_list = selected_areas + + async def read_and_validate_current_area(self, step): + self.print_step(step, "Read CurrentArea attribute") + current_area = await self.read_sear_attribute_expect_success( + endpoint=self.endpoint, attribute=Clusters.ServiceArea.Attributes.CurrentArea) + logging.info(f"CurrentArea {current_area}") + + asserts.assert_true((len(self.selareaid_list) == 0 and current_area is NullValue) + or + current_area in self.selareaid_list, + f"CurrentArea {current_area} is invalid. SelectedAreas is {self.selareaid_list}.") + # save so other methods can use this if neeeded + self.current_area = current_area + + async def read_and_validate_estimated_end_time(self, step): + import time + read_time = int(time.time()) + self.print_step(step, "Read EstimatedEndTime attribute") + estimated_end_time = await self.read_sear_attribute_expect_success( + endpoint=self.endpoint, attribute=Clusters.ServiceArea.Attributes.EstimatedEndTime) + logging.info(f"EstimatedEndTime {estimated_end_time}") + + if self.current_area is NullValue: + asserts.assert_true(estimated_end_time is NullValue, + "EstimatedEndTime should be null if CurrentArea is null.") + else: + # allow for some clock skew + asserts.assert_true(estimated_end_time >= read_time - 3*60, + f"EstimatedEndTime({estimated_end_time}) should be greater than the time when it was read({read_time})") + + async def read_and_validate_progress(self, step): + self.print_step(step, "Read Progress attribute") + progress = await self.read_sear_attribute_expect_success( + endpoint=self.endpoint, attribute=Clusters.ServiceArea.Attributes.Progress) + logging.info(f"Progress {progress}") + + asserts.assert_true(len(progress) <= len(self.areaid_list), + f"Progress(len {len(progress)}) should have at most {len(self.areaid_list)} entries") + + progareaid_list = [] + for p in progress: + if p.areaID in progareaid_list: + asserts.fail("Progress must have unique AreaID values!") + else: + progareaid_list.append(p.areaID) + asserts.assert_true(p.areaID in self.areaid_list, + f"Progress entry has invalid AreaID value ({p.areaID})") + asserts.assert_true(p.status in (Clusters.ServiceArea.OperationalStatusEnum.kPending, + Clusters.ServiceArea.OperationalStatusEnum.kOperating, + Clusters.ServiceArea.OperationalStatusEnum.kSkipped, + Clusters.ServiceArea.OperationalStatusEnum.kCompleted), + f"Progress entry has invalid Status value ({p.status})") + if p.status not in (Clusters.ServiceArea.OperationalStatusEnum.kSkipped, Clusters.ServiceArea.OperationalStatusEnum.kCompleted): + asserts.assert_true(p.totalOperationalTime is NullValue, + f"Progress entry should have a null TotalOperationalTime value (Status is {p.status})") + # TODO how to check that InitialTimeEstimate is either null or uint32? + + # Sends and out-of-band command to the rvc-app + def write_to_app_pipe(self, command): + with open(self.app_pipe, "w") as app_pipe: + app_pipe.write(command + "\n") + # Allow some time for the command to take effect. + # This removes the test flakyness which is very annoying for everyone in CI. + sleep(0.001) + + def TC_SEAR_1_2(self) -> list[str]: + return ["SEAR.S"] + + @async_test_body + async def test_TC_SEAR_1_2(self): + self.endpoint = self.matter_test_config.endpoint + asserts.assert_false(self.endpoint is None, "--endpoint must be included on the command line in.") + self.is_ci = self.check_pics("PICS_SDK_CI_ONLY") + if self.is_ci: + app_pid = self.matter_test_config.app_pid + if app_pid == 0: + asserts.fail("The --app-pid flag must be set when PICS_SDK_CI_ONLY is set") + self.app_pipe = self.app_pipe + str(app_pid) + + self.print_step(1, "Commissioning, already done") + + # Ensure that the device is in the correct state + if self.is_ci: + self.write_to_app_pipe('{"Name": "Reset"}') + + if self.check_pics("SEAR.S.F02"): + await self.read_and_validate_supported_maps(step=2) + + await self.read_and_validate_supported_areas(step=3) + + await self.read_and_validate_selected_areas(step=4) + + if self.check_pics("SEAR.S.A0003"): + await self.read_and_validate_current_area(step=5) + + if self.check_pics("SEAR.S.A0004"): + await self.read_and_validate_estimated_end_time(step=6) + + if self.check_pics("SEAR.S.A0005"): + await self.read_and_validate_progress(step=7) + + if self.check_pics("SEAR.S.F02") and self.check_pics("SEAR.S.M.REMOVE_MAP"): + test_step = "Manually ensure the SupportedMaps attribute is not empty and that the device is not operating" + self.print_step("8", test_step) + if not self.is_ci: + self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n") + + await self.read_and_validate_supported_maps(step=9) + old_supported_maps = self.mapid_list + + test_step = "Manually intervene to remove one or more entries in the SupportedMaps list" + self.print_step("10", test_step) + if not self.is_ci: + self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n") + + await self.read_and_validate_supported_maps(step=11) + new_supported_maps = self.mapid_list + asserts.assert_true(len(old_supported_maps) > len(new_supported_maps), "Failed to remove map(s)") + + # NOTE the following operations are all part of step 11 - read all these attributes and check the data consistency + # after removing map(s) + await self.read_and_validate_supported_areas(step=11) + + await self.read_and_validate_selected_areas(step=11) + + if self.check_pics("SEAR.S.A0003"): + await self.read_and_validate_current_area(step=11) + + if self.check_pics("SEAR.S.A0004"): + await self.read_and_validate_estimated_end_time(step=11) + + if self.check_pics("SEAR.S.A0005"): + await self.read_and_validate_progress(step=11) + + if self.check_pics("SEAR.S.F02") and self.check_pics("SEAR.S.M.ADD_MAP"): + test_step = "Manually ensure the SupportedMaps attribute has less than 255 entries and that the device is not operating" + self.print_step("12", test_step) + if not self.is_ci: + self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n") + + await self.read_and_validate_supported_maps(step=13) + old_supported_maps = self.mapid_list + + test_step = "Manually intervene to add one or more entries to the SupportedMaps list" + self.print_step("14", test_step) + if not self.is_ci: + self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n") + + await self.read_and_validate_supported_maps(step=15) + new_supported_maps = self.mapid_list + asserts.assert_true(len(old_supported_maps) < len(new_supported_maps), "Failed to add map(s)") + + # NOTE the following operations are all part of step 15 - read all these attributes and check the data consistency + # after adding map(s) + await self.read_and_validate_supported_areas(step=15) + + await self.read_and_validate_selected_areas(step=15) + + if self.check_pics("SEAR.S.A0003"): + await self.read_and_validate_current_area(step=15) + + if self.check_pics("SEAR.S.A0004"): + await self.read_and_validate_estimated_end_time(step=15) + + if self.check_pics("SEAR.S.A0005"): + await self.read_and_validate_progress(step=15) + + if self.check_pics("SEAR.S.M.REMOVE_AREA"): + test_step = "Manually ensure the SupportedAreas attribute is not empty and that the device is not operating" + self.print_step("16", test_step) + if not self.is_ci: + self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n") + + await self.read_and_validate_supported_areas(step=17) + old_supported_areas = self.areaid_list + + test_step = "Manually intervene to remove one or more entries from the SupportedAreas list" + self.print_step("18", test_step) + if not self.is_ci: + self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n") + + await self.read_and_validate_supported_areas(step=19) + new_supported_areas = self.areaid_list + asserts.assert_true(len(old_supported_areas) > len(new_supported_areas), "Failed to remove area(s)") + + # NOTE the following operations are all part of step 19 - read all these attributes and check the data consistency + # after removing areas(s) + + await self.read_and_validate_selected_areas(step=19) + + if self.check_pics("SEAR.S.A0003"): + await self.read_and_validate_current_area(step=19) + + if self.check_pics("SEAR.S.A0004"): + await self.read_and_validate_estimated_end_time(step=19) + + if self.check_pics("SEAR.S.A0005"): + await self.read_and_validate_progress(step=19) + + if self.check_pics("SEAR.S.M.ADD_AREA"): + test_step = "Manually ensure the SupportedAreas attribute has less than 255 entries and that the device is not operating" + self.print_step("20", test_step) + if not self.is_ci: + self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n") + + await self.read_and_validate_supported_areas(step=21) + old_supported_areas = self.areaid_list + + test_step = "Manually intervene to add one or more entries to the SupportedAreas list" + self.print_step("22", test_step) + if not self.is_ci: + self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n") + + await self.read_and_validate_supported_areas(step=23) + new_supported_areas = self.areaid_list + asserts.assert_true(len(old_supported_areas) < len(new_supported_areas), "Failed to add area(s)") + + # NOTE the following operations are all part of step 23 - read all these attributes and check the data consistency + # after removing areas(s) + + await self.read_and_validate_selected_areas(step=23) + + if self.check_pics("SEAR.S.A0003"): + await self.read_and_validate_current_area(step=23) + + if self.check_pics("SEAR.S.A0004"): + await self.read_and_validate_estimated_end_time(step=23) + + if self.check_pics("SEAR.S.A0005"): + await self.read_and_validate_progress(step=23) + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_SEAR_1_3.py b/src/python_testing/TC_SEAR_1_3.py new file mode 100644 index 00000000000000..0acee1b34cdeb8 --- /dev/null +++ b/src/python_testing/TC_SEAR_1_3.py @@ -0,0 +1,155 @@ +# +# 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. +# + +# TODO - this was copied/pasted from abother test, it needs to be reviewed and updated +# 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 +# test-runner-run/run1/app: ${CHIP_RVC_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/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:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + +import logging +from time import sleep + +import chip.clusters as Clusters +from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main +from mobly import asserts + + +class TC_SEAR_1_3(MatterBaseTest): + def __init__(self, *args): + super().__init__(*args) + self.endpoint = None + self.is_ci = False + self.app_pipe = "/tmp/chip_rvc_fifo_" + + async def read_sear_attribute_expect_success(self, endpoint, attribute): + cluster = Clusters.Objects.ServiceArea + return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=attribute) + + async def read_supported_areas(self, step): + self.print_step(step, "Read SupportedAreas attribute") + supported_areas = await self.read_sear_attribute_expect_success( + endpoint=self.endpoint, attribute=Clusters.ServiceArea.Attributes.SupportedAreas) + logging.info("SupportedAreas: %s" % (supported_areas)) + + return [a.areaID for a in supported_areas] + + async def read_selected_areas(self, step): + self.print_step(step, "Read SelectedAreas attribute") + selected_areas = await self.read_sear_attribute_expect_success( + endpoint=self.endpoint, attribute=Clusters.ServiceArea.Attributes.SelectedAreas) + logging.info(f"SelectedAreas {selected_areas}") + + return selected_areas + + async def send_cmd_select_areas_expect_response(self, step, new_areas, expected_response): + self.print_step(step, f"Send SelectAreas command with NewAreas({new_areas})") + ret = await self.send_single_cmd(cmd=Clusters.Objects.ServiceArea.Commands.SelectAreas(newAreas=new_areas), + endpoint=self.endpoint) + + asserts.assert_equal(ret.commandResponseState.errorStateID, + expected_response, + f"Command response ({ret.commandResponseState}) doesn't match the expected one") + + # Sends and out-of-band command to the rvc-app + def write_to_app_pipe(self, command): + with open(self.app_pipe, "w") as app_pipe: + app_pipe.write(command + "\n") + # Allow some time for the command to take effect. + # This removes the test flakyness which is very annoying for everyone in CI. + sleep(0.001) + + def TC_SEAR_1_3(self) -> list[str]: + return ["SEAR.S"] + + @async_test_body + async def test_TC_SEAR_1_3(self): + self.endpoint = self.matter_test_config.endpoint + asserts.assert_false(self.endpoint is None, "--endpoint must be included on the command line in.") + self.is_ci = self.check_pics("PICS_SDK_CI_ONLY") + if self.is_ci: + app_pid = self.matter_test_config.app_pid + if app_pid == 0: + asserts.fail("The --app-pid flag must be set when PICS_SDK_CI_ONLY is set") + self.app_pipe = self.app_pipe + str(app_pid) + + self.print_step(1, "Commissioning, already done") + + # Ensure that the device is in the correct state + if self.is_ci: + self.write_to_app_pipe('{"Name": "Reset"}') + + supported_area_ids = await self.read_supported_areas(step=2) + asserts.assert_true(len(self.supported_areas) > 0, "SupportedAreas is empty") + valid_area_id = supported_area_ids[0] + invalid_area_id = 1 + max(supported_area_ids) + + duplicated_areas = [valid_area_id, valid_area_id] + + # FIXME need to check if this is the correct name of this status code + await self.send_cmd_select_areas_expect_response(step=3, new_areas=duplicated_areas, expected_response=Clusters.ServiceArea.SelectAreasStatus.kDuplicatedAreas) + + await self.send_cmd_select_areas_expect_response(step=4, new_areas=[], expected_response=Clusters.ServiceArea.SelectAreasStatus.kSuccess) + + selected_areas = await self.read_selected_areas(step=5) + asserts.assert_true(len(selected_areas) == 0, "SelectedAreas should be empty") + + await self.send_cmd_select_areas_expect_response(step=6, new_areas=[invalid_area_id], expected_response=Clusters.ServiceArea.SelectAreasStatus.kUnsupportedArea) + + if self.check_pics("SEAR.S.M.INVALID_STATE_FOR_SELECT_AREAS") and self.check_pics("SEAR.S.M.HAS_MANUAL_SELAREA_STATE_CONTROL"): + test_step = "Manually intervene to put the device in a state that prevents it from executing the SelectAreas command" + self.print_step("7", test_step) + if not self.is_ci: + self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n") + + await self.send_cmd_select_areas_expect_response(step=8, new_areas=[valid_area_id], expected_response=Clusters.ServiceArea.SelectAreasStatus.kInvalidInMode) + + if self.check_pics("SEAR.S.M.VALID_STATE_FOR_SELECT_AREAS") and self.check_pics("SEAR.S.M.HAS_MANUAL_SELAREA_STATE_CONTROL"): + test_step = f"Manually intervene to put the device in a state that allows it to execute the SelectAreas({supported_area_ids}) command" + self.print_step("9", test_step) + if not self.is_ci: + self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n") + + await self.send_cmd_select_areas_expect_response(step=10, new_areas=supported_area_ids, expected_response=Clusters.ServiceArea.SelectAreasStatus.kSuccess) + + selected_areas = await self.read_selected_areas(step=11) + asserts.assert_true(len(selected_areas) == len(supported_area_ids), + f"SelectedAreas({selected_areas}) should match SupportedAreas({supported_area_ids})") + + await self.send_cmd_select_areas_expect_response(step=12, new_areas=supported_area_ids, expected_response=Clusters.ServiceArea.SelectAreasStatus.kSuccess) + + if self.check_pics("SEAR.S.M.VALID_STATE_FOR_SELECT_AREAS") and self.check_pics("SEAR.S.M.HAS_MANUAL_SELAREA_STATE_CONTROL") and self.check_pics("SEAR.S.M.SELECT_AREAS_WHILE_NON_IDLE"): + test_step = f"Manually intervene to put the device in a state that allows it to execute the SelectAreas({valid_area_id}) command, and put the device in a non-idle state" + self.print_step("13", test_step) + if not self.is_ci: + self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n") + + if self.check_pics("SEAR.S.F00"): + await self.send_cmd_select_areas_expect_response(step=14, new_areas=[valid_area_id], expected_response=Clusters.ServiceArea.SelectAreasStatus.kSuccess) + else: + await self.send_cmd_select_areas_expect_response(step=14, new_areas=[valid_area_id], expected_response=Clusters.ServiceArea.SelectAreasStatus.kInvalidInMode) + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_SEAR_1_4.py b/src/python_testing/TC_SEAR_1_4.py new file mode 100644 index 00000000000000..a6326559beaa20 --- /dev/null +++ b/src/python_testing/TC_SEAR_1_4.py @@ -0,0 +1,84 @@ +# +# 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. +# + +# TODO - this was copied/pasted from abother test, it needs to be reviewed and updated +# 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 +# test-runner-run/run1/app: ${CHIP_RVC_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/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:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + +import logging + +import chip.clusters as Clusters +from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main +from mobly import asserts + + +class TC_SEAR_1_4(MatterBaseTest): + def __init__(self, *args): + super().__init__(*args) + self.endpoint = None + self.is_ci = False + self.app_pipe = "/tmp/chip_rvc_fifo_" + + async def read_sear_attribute_expect_success(self, endpoint, attribute): + cluster = Clusters.Objects.ServiceArea + return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=attribute) + + def TC_SEAR_1_4(self) -> list[str]: + return ["SEAR.S"] + + @async_test_body + async def test_TC_SEAR_1_4(self): + self.endpoint = self.matter_test_config.endpoint + asserts.assert_false(self.endpoint is None, "--endpoint must be included on the command line in.") + self.is_ci = self.check_pics("PICS_SDK_CI_ONLY") + if self.is_ci: + app_pid = self.matter_test_config.app_pid + if app_pid == 0: + asserts.fail("The --app-pid flag must be set when PICS_SDK_CI_ONLY is set") + self.app_pipe = self.app_pipe + str(app_pid) + + self.print_step(1, "Commissioning, already done") + + # Ensure that the device is in the correct state + if self.is_ci: + self.write_to_app_pipe('{"Name": "Reset"}') + + attribute_list = await self.read_sear_attribute_expect_success( + endpoint=self.endpoint, attribute=Clusters.ServiceArea.Attributes.AttributeList) + logging.info("AttributeList: %s" % (attribute_list)) + + if Clusters.ServiceArea.Attributes.CurrentArea not in attribute_list \ + and Clusters.ServiceArea.Attributes.Progress not in attribute_list: + + cmd_list = await self.read_sear_attribute_expect_success( + endpoint=self.endpoint, attribute=Clusters.ServiceArea.Attributes.AcceptedCommandList) + logging.info("AcceptedCommandList: %s" % (cmd_list)) + asserts.assert_true(Clusters.ServiceArea.Commands.SkipArea not in cmd_list, + "SkipArea command should not be implemented if both CurrentArea and Progress are not") + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_SEAR_1_5.py b/src/python_testing/TC_SEAR_1_5.py new file mode 100644 index 00000000000000..adcf8341c93389 --- /dev/null +++ b/src/python_testing/TC_SEAR_1_5.py @@ -0,0 +1,283 @@ +# +# 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. +# + +# TODO - this was copied/pasted from abother test, it needs to be reviewed and updated +# 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 +# test-runner-run/run1/app: ${CHIP_RVC_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/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:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + +import logging +from time import sleep + +import chip.clusters as Clusters +from chip.clusters.Types import NullValue +from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main +from mobly import asserts + + +class TC_SEAR_1_5(MatterBaseTest): + def __init__(self, *args): + super().__init__(*args) + self.endpoint = None + self.is_ci = False + self.app_pipe = "/tmp/chip_rvc_fifo_" + + async def read_sear_attribute_expect_success(self, endpoint, attribute): + cluster = Clusters.Objects.ServiceArea + return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=attribute) + + async def read_supported_areas(self, step): + self.print_step(step, "Read SupportedAreas attribute") + supported_areas = await self.read_sear_attribute_expect_success( + endpoint=self.endpoint, attribute=Clusters.ServiceArea.Attributes.SupportedAreas) + logging.info("SupportedAreas: %s" % (supported_areas)) + + return [a.areaID for a in supported_areas] + + async def read_selected_areas(self, step): + self.print_step(step, "Read SelectedAreas attribute") + selected_areas = await self.read_sear_attribute_expect_success( + endpoint=self.endpoint, attribute=Clusters.ServiceArea.Attributes.SelectedAreas) + logging.info(f"SelectedAreas {selected_areas}") + + return selected_areas + + async def read_progress(self, step): + self.print_step(step, "Read Progress attribute") + progress = await self.read_sear_attribute_expect_success( + endpoint=self.endpoint, attribute=Clusters.ServiceArea.Attributes.Progress) + logging.info(f"Progress {progress}") + + return progress + + async def read_current_area(self, step): + self.print_step(step, "Read CurrentArea attribute") + current_area = await self.read_sear_attribute_expect_success( + endpoint=self.endpoint, attribute=Clusters.ServiceArea.Attributes.CurrentArea) + logging.info(f"CurrentArea {current_area}") + + return current_area + + async def send_cmd_skip_area_expect_response(self, step, skipped_area, expected_response): + self.print_step(step, f"Send SkipArea command with SkippedArea({skipped_area})") + ret = await self.send_single_cmd(cmd=Clusters.Objects.ServiceArea.Commands.SkipArea(skippedArea=skipped_area), + endpoint=self.endpoint) + + asserts.assert_equal(ret.commandResponseState.errorStateID, + expected_response, + f"Command response ({ret.commandResponseState}) doesn't match the expected one") + + # Sends and out-of-band command to the rvc-app + + def write_to_app_pipe(self, command): + with open(self.app_pipe, "w") as app_pipe: + app_pipe.write(command + "\n") + # Allow some time for the command to take effect. + # This removes the test flakyness which is very annoying for everyone in CI. + sleep(0.001) + + def TC_SEAR_1_5(self) -> list[str]: + return ["SEAR.S", "SEAR.S.C02.Rsp"] + + @async_test_body + async def test_TC_SEAR_1_5(self): + self.endpoint = self.matter_test_config.endpoint + asserts.assert_false(self.endpoint is None, "--endpoint must be included on the command line in.") + self.is_ci = self.check_pics("PICS_SDK_CI_ONLY") + if self.is_ci: + app_pid = self.matter_test_config.app_pid + if app_pid == 0: + asserts.fail("The --app-pid flag must be set when PICS_SDK_CI_ONLY is set") + self.app_pipe = self.app_pipe + str(app_pid) + + self.print_step(1, "Commissioning, already done") + + # Ensure that the device is in the correct state + if self.is_ci: + self.write_to_app_pipe('{"Name": "Reset"}') + + supported_area_ids = await self.read_supported_areas(step=2) + asserts.assert_true(len(supported_area_ids) > 0, "SupportedAreas is empty") + valid_area_id = supported_area_ids[0] + invalid_area_id = 1 + max(supported_area_ids) + + if self.check_pics("SEAR.S.M.INVALID_STATE_FOR_SKIP") and self.check_pics("SEAR.S.M.HAS_MANUAL_SKIP_STATE_CONTROL"): + test_step = "Manually intervene to put the device in a state that prevents it from executing the SkipArea command \ + (e.g. set CurrentArea to null or make it not operate, i.e. be in the idle state)" + self.print_step("3", test_step) + if not self.is_ci: + self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n") + + await self.send_cmd_skip_area_expect_response(step=4, skipped_area=valid_area_id, + expected_response=Clusters.ServiceArea.SkipAreaStatus.kInvalidInMode) + + if self.check_pics("SEAR.S.M.NO_SELAREA_FOR_SKIP") and self.check_pics("SEAR.S.M.HAS_MANUAL_SKIP_STATE_CONTROL"): + test_step = "Manually intervene to put the device in a state where the state would allow it to execute the SkipArea command, \ + if SelectedAreas wasn't empty, and SelectedAreas is empty" + self.print_step("5", test_step) + if not self.is_ci: + self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n") + + await self.send_cmd_skip_area_expect_response(step=6, skipped_area=valid_area_id, + expected_response=Clusters.ServiceArea.SkipAreaStatus.kInvalidAreaList) + + if self.check_pics("SEAR.S.M.VALID_STATE_FOR_SKIP") and self.check_pics("SEAR.S.M.HAS_MANUAL_SKIP_STATE_CONTROL"): + test_step = "Manually intervene to put the device in a state that allows it to execute the SkipArea command" + self.print_step("7", test_step) + if not self.is_ci: + self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n") + + await self.send_cmd_skip_area_expect_response(step=8, skipped_area=invalid_area_id, + expected_response=Clusters.ServiceArea.SkipAreaStatus.kInvalidSkippedArea) + + if not self.check_pics("SEAR.S.M.VALID_STATE_FOR_SKIP"): + return + + if self.check_pics("SEAR.S.A0005"): + old_progress_list = await self.read_progress(step=9) + asserts.assert_true(len(old_progress_list) > 0, f"len of Progress({len(old_progress_list)}) should not be zero)") + + selected_areas = await self.read_selected_areas(step=10) + asserts.assert_true(len(selected_areas) > 0, "SelectedAreas is empty") + + old_current_area = NullValue + if self.check_pics("SEAR.S.A0003"): + old_current_area = await self.read_current_area(step=11) + + self.print_step("12", "") + if old_current_area is not NullValue: + await self.send_cmd_skip_area_expect_response(step=13, skipped_area=old_current_area, + expected_response=Clusters.ServiceArea.SkipAreaStatus.kSuccess) + if self.check_pics("SEAR.S.M.HAS_MANUAL_SKIP_STATE_CONTROL"): + test_step = "(Manual operation) wait for the device to skip the current area, and start operating at\ + the next one it should process, or stop operating" + self.print_step("14", test_step) + if not self.is_ci: + self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n") + + if self.check_pics("SEAR.S.A0005"): + new_progress_list = await self.read_progress(step=15) + asserts.assert_true(len(new_progress_list) > 0, + f"len of Progress({len(new_progress_list)}) should not be zero)") + + prog_areas = [p.areaID for p in new_progress_list] + + asserts.assert_true(old_current_area in prog_areas, f"Progress should include area {old_current_area}") + + new_current_area = await self.read_current_area(step=16) + for p in new_progress_list: + if p.areaID == old_current_area: + asserts.assert_true(p.status == Clusters.ServiceArea.OperationalStatusEnum.kSkipped, + "Progress for areaID({old_current_area}) should be Skipped") + break + test_step = "Indicate whether the device has stopped operating (y/n)" + ret = self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n") + + # Verify that if the device hasn't stopped operating, the `new_progress_list`'s entry matching `new_current_area` shows the Operating status + if ret != "y": + for p in new_progress_list: + if p.areaID == new_current_area: + asserts.assert_true(p.status == Clusters.ServiceArea.OperationalStatusEnum.kOperating, + "Progress for areaID({new_current_area}) should be Operating") + break + + # next, we need to check for something else (so the condition of the 'if' above becomes part of the 'then' statement below): + # if before skipping all areas, except the current one, were Skipped or Completed, the device MUST have stopped operating + was_only_skipped_or_completed = True + for p in old_progress_list: + if p.areaID != old_current_area: + if p.status not in (Clusters.ServiceArea.OperationalStatusEnum.kSkipped, + Clusters.ServiceArea.OperationalStatusEnum.kCompleted): + was_only_skipped_or_completed = False + break + if was_only_skipped_or_completed: + asserts.assert_true(ret == "y", "The device should not be operating") + + self.print_step("17", "") + return + + if not self.check_pics("SEAR.S.A0005"): + return + + if self.check_pics("SEAR.S.M.HAS_MANUAL_SKIP_STATE_CONTROL"): + test_step = "Manually intervene to put the device in a state that allows it to execute the SkipArea command" + self.print_step("18", test_step) + if not self.is_ci: + self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n") + + self.print_step("19", "") + if len(old_progress_list) == 0: + return + + area_to_skip = NullValue + self.print_step("20", "") + for p in old_progress_list: + if p.status in (Clusters.ServiceArea.OperationalStatusEnum.kPending, + Clusters.ServiceArea.OperationalStatusEnum.kOperating): + area_to_skip = p.areaID + break + + if area_to_skip is NullValue: + return + + await self.send_cmd_skip_area_expect_response(step=21, skipped_area=area_to_skip, + expected_response=Clusters.ServiceArea.SkipAreaStatus.kSuccess) + + if self.check_pics("SEAR.S.M.HAS_MANUAL_SKIP_STATE_CONTROL"): + test_step = "(Manual operation) wait for the device to update Progress or to stop operating" + self.print_step("22", test_step) + if not self.is_ci: + self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n") + + new_progress_list = await self.read_progress(step=23) + asserts.assert_true(len(new_progress_list) > 0, f"len of Progress({len(new_progress_list)}) should not be zero)") + + for p in new_progress_list: + if p.areaID == area_to_skip: + asserts.assert_true(p.status == Clusters.ServiceArea.OperationalStatusEnum.kSkipped, + "Progress for areaID({new_current_area}) should be Skipped") + break + + test_step = "Indicate whether the device has stopped operating (y/n)" + ret = self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n") + + was_only_skipped_or_completed = True + for p in old_progress_list: + if p.areaID != area_to_skip: + if p.status not in (Clusters.ServiceArea.OperationalStatusEnum.kSkipped, + Clusters.ServiceArea.OperationalStatusEnum.kCompleted): + was_only_skipped_or_completed = False + break + if was_only_skipped_or_completed: + asserts.assert_true(ret == "y", "The device should not be operating") + for p in new_progress_list: + if p.areaID == old_current_area: + asserts.assert_true(p.status == Clusters.ServiceArea.OperationalStatusEnum.kSkipped, + "Progress for areaID({old_current_area}) should be Skipped") + break + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_SEAR_1_6.py b/src/python_testing/TC_SEAR_1_6.py new file mode 100644 index 00000000000000..02a0fcdd33133a --- /dev/null +++ b/src/python_testing/TC_SEAR_1_6.py @@ -0,0 +1,148 @@ +# +# 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. +# + +# TODO - this was copied/pasted from abother test, it needs to be reviewed and updated +# 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 +# test-runner-run/run1/app: ${CHIP_RVC_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/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:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + +import logging +from time import sleep + +import chip.clusters as Clusters +from chip.clusters.Types import NullValue +from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main +from mobly import asserts + + +class TC_SEAR_1_6(MatterBaseTest): + def __init__(self, *args): + super().__init__(*args) + self.endpoint = None + self.is_ci = False + self.app_pipe = "/tmp/chip_rvc_fifo_" + + async def read_sear_attribute_expect_success(self, endpoint, attribute): + cluster = Clusters.Objects.ServiceArea + return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=attribute) + + async def read_supported_areas(self, step): + self.print_step(step, "Read SupportedAreas attribute") + supported_areas = await self.read_sear_attribute_expect_success( + endpoint=self.endpoint, attribute=Clusters.ServiceArea.Attributes.SupportedAreas) + logging.info("SupportedAreas: %s" % (supported_areas)) + + return [a.areaID for a in supported_areas] + + async def read_selected_areas(self, step): + self.print_step(step, "Read SelectedAreas attribute") + selected_areas = await self.read_sear_attribute_expect_success( + endpoint=self.endpoint, attribute=Clusters.ServiceArea.Attributes.SelectedAreas) + logging.info(f"SelectedAreas {selected_areas}") + + return selected_areas + + async def read_progress(self, step): + self.print_step(step, "Read Progress attribute") + progress = await self.read_sear_attribute_expect_success( + endpoint=self.endpoint, attribute=Clusters.ServiceArea.Attributes.Progress) + logging.info(f"Progress {progress}") + + return progress + + # Sends and out-of-band command to the rvc-app + def write_to_app_pipe(self, command): + with open(self.app_pipe, "w") as app_pipe: + app_pipe.write(command + "\n") + # Allow some time for the command to take effect. + # This removes the test flakyness which is very annoying for everyone in CI. + sleep(0.001) + + def TC_SEAR_1_6(self) -> list[str]: + return ["SEAR.S", "SEAR.S.A0005", "SEAR.S.A0000", "SEAR.S.A0002", "SEAR.S.M.HAS_MANUAL_OPERATING_STATE_CONTROL"] + + @async_test_body + async def test_TC_SEAR_1_6(self): + self.endpoint = self.matter_test_config.endpoint + asserts.assert_false(self.endpoint is None, "--endpoint must be included on the command line in.") + self.is_ci = self.check_pics("PICS_SDK_CI_ONLY") + if self.is_ci: + app_pid = self.matter_test_config.app_pid + if app_pid == 0: + asserts.fail("The --app-pid flag must be set when PICS_SDK_CI_ONLY is set") + self.app_pipe = self.app_pipe + str(app_pid) + + self.print_step(1, "Commissioning, already done") + + # Ensure that the device is in the correct state + if self.is_ci: + self.write_to_app_pipe('{"Name": "Reset"}') + + test_step = "Manually intervene to put the device in the idle state and ensure SupportedAreas and SelectedAreas are not empty" + self.print_step("2", test_step) + if not self.is_ci: + self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n") + + supported_area_ids = await self.read_supported_areas(step=3) + asserts.assert_true(len(supported_area_ids) > 0, "SupportedAreas is empty") + + selected_areas = await self.read_selected_areas(step=4) + asserts.assert_true(len(selected_areas) > 0, "SelectedAreas is empty") + + test_step = "Manually intervene to put the device in the operating state" + self.print_step("5", test_step) + if not self.is_ci: + self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n") + + progress_list_operating = await self.read_progress(step=6) + asserts.assert_true(len(selected_areas) == len(progress_list_operating), + f"len of SelectedAreas({len(selected_areas)}) should be equal to len of Progress({len(progress_list_operating)})") + + for p in progress_list_operating: + asserts.assert_true(p.areaID in selected_areas, f"Progress entry with unknown AreaID({p.areaID})") + asserts.assert_true(p.status in (Clusters.ServiceArea.OperationalStatusEnum.kPending, + Clusters.ServiceArea.OperationalStatusEnum.kOperating), + f"Progress entry with unexpected Status({p.status})") + asserts.assert_true(p.TotalOperationalTime is NullValue, "Progress entry with non-null TotalOperationalTime") + + test_step = "While all entries in Progress show the Pending or Operating status (i.e. \ + before any area is skipped or completed), manually intervene to put the device \ + in the idle state, by ending the operation unexpectedly (e.g. force an error)" + self.print_step("7", test_step) + if not self.is_ci: + self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n") + + progress_list_idle = await self.read_progress(step=8) + asserts.assert_true(len(selected_areas) == len(progress_list_idle), + f"len of SelectedAreas({len(selected_areas)}) should be equal to len of Progress({len(progress_list_idle)})") + + for p in progress_list_idle: + asserts.assert_true(p.areaID in selected_areas, f"Progress entry with unknown AreaID({p.areaID})") + asserts.assert_true(p.status == Clusters.ServiceArea.OperationalStatusEnum.kSkipped, + f"Progress entry with unexpected Status({p.status})") + + +if __name__ == "__main__": + default_matter_test_main()