diff --git a/app/chip_tool/chip_tool.py b/app/chip_tool/chip_tool.py index f1bec756..dbff77d6 100644 --- a/app/chip_tool/chip_tool.py +++ b/app/chip_tool/chip_tool.py @@ -107,6 +107,17 @@ XML_SPEC_DEFINITION_PATH = TEST_COLLECTION_SDK_CHECKOUT_PATH / Path( "sdk_runner/specifications/chip/" ) +# Python Testing Folder +# LOCAL_PYTHON_TESTING_PATH = TEST_COLLECTION_SDK_CHECKOUT_PATH / Path( +# "python_testing/scripts/sdk" +# ) +# DOCKER_PYTHON_TESTING_PATH = "/root/python_testing" + +# RPC Client Running on SDK Container +LOCAL_RPC_PYTHON_TESTING_PATH = BACKEND_ROOT / Path( + "test_collections/sdk_tests/support/python_testing/models/rpc_client/" +) +DOCKER_RPC_PYTHON_TESTING_PATH = "/root/python_testing/rpc_client" # Docker Network @@ -132,6 +143,7 @@ class ChipToolUnknownTestType(Exception): class ChipToolTestType(str, Enum): CHIP_TOOL = "chip-tool" CHIP_APP = "chip-app" + PYTHON_TEST = "python-test" class ChipTool(metaclass=Singleton): @@ -168,6 +180,10 @@ class ChipTool(metaclass=Singleton): "bind": DOCKER_CREDENTIALS_DEVELOPMENT_PATH, "mode": "ro", }, + LOCAL_RPC_PYTHON_TESTING_PATH: { + "bind": DOCKER_RPC_PYTHON_TESTING_PATH, + "mode": "rw", + }, }, } @@ -345,7 +361,7 @@ def __get_gateway_ip(self) -> str: .get(DOCKER_GATEWAY_KEY, "") ) - async def start_container_no_server(self) -> None: + async def start_container(self) -> None: """ Creates the chip-tool container without any server running (ChipTool or ChipApp). @@ -371,7 +387,7 @@ async def start_container_no_server(self) -> None: # Server started is false after spinning up a new container. self.__server_started = False - async def start_container( + async def start_server( self, test_type: ChipToolTestType, use_paa_certs: bool = False ) -> None: """Creates the chip-tool container. @@ -384,7 +400,7 @@ async def start_container( ) return - await self.start_container_no_server() + await self.start_container() web_socket_config = WebSocketRunnerConfig() web_socket_config.server_address = self.__get_gateway_ip() @@ -587,7 +603,7 @@ async def run_test( if test_type == ChipToolTestType.CHIP_TOOL: test_path = f"{YAML_TESTS_PATH}/{test_id}.yaml" - else: + elif test_type == ChipToolTestType.CHIP_APP: test_path = f"{YAML_TESTS_PATH}/{test_id}_Simulated.yaml" parser_config = TestParserConfig(pics_path, self.specifications, test_options) diff --git a/app/chip_tool/test_suite.py b/app/chip_tool/test_suite.py index 7fb57a85..c99af552 100644 --- a/app/chip_tool/test_suite.py +++ b/app/chip_tool/test_suite.py @@ -54,7 +54,7 @@ async def setup(self) -> None: logger.info("Setting up chip_tool") # Use test engine logger to log all events to test run. self.chip_tool.logger = logger - await self.chip_tool.start_container( + await self.chip_tool.start_server( self.test_type, self.config.dut_config.chip_tool_use_paa_certs ) @@ -284,3 +284,4 @@ async def __prompt_user_to_perform_decommission(self) -> None: f"Received unknown prompt option for \ decommissioning step: {prompt_response.response}" ) + diff --git a/app/tests/chip_tool/test_chip_tool.py b/app/tests/chip_tool/test_chip_tool.py index 139e8899..461bfbd2 100644 --- a/app/tests/chip_tool/test_chip_tool.py +++ b/app/tests/chip_tool/test_chip_tool.py @@ -69,7 +69,7 @@ async def test_start_container() -> None: ) as mock_create_container, mock.patch.object( target=chip_tool, attribute="start_chip_server" ) as mock_start_chip_server: - await chip_tool.start_container(test_type) + await chip_tool.start_server(test_type) mock_create_container.assert_called_once_with(docker_image, ChipTool.run_parameters) mock_start_chip_server.assert_awaited_once_with(test_type, False) @@ -98,7 +98,7 @@ async def test_start_container_using_paa_certs() -> None: ) as mock_create_container, mock.patch.object( target=chip_tool, attribute="start_chip_server" ) as mock_start_chip_server: - await chip_tool.start_container(test_type, use_paa_certs=True) + await chip_tool.start_server(test_type, use_paa_certs=True) mock_create_container.assert_called_once_with(docker_image, ChipTool.run_parameters) mock_start_chip_server.assert_awaited_once_with(test_type, True) @@ -120,7 +120,7 @@ async def test_not_start_container_when_running() -> None: ) as mock_create_container, mock.patch.object( target=chip_tool, attribute="start_chip_server" ) as mock_start_chip_server: - await chip_tool.start_container(test_type) + await chip_tool.start_server(test_type) mock_create_container.assert_not_called() mock_start_chip_server.assert_not_called() @@ -359,8 +359,8 @@ async def test_destroy_container_running() -> None: ), mock.patch.object( target=chip_tool, attribute="start_chip_server" ): - await chip_tool.start_container(test_type) - await chip_tool.start_container(test_type) + await chip_tool.start_server(test_type) + await chip_tool.start_server(test_type) assert chip_tool._ChipTool__chip_tool_container is not None @@ -403,7 +403,7 @@ async def test_destroy_container_once() -> None: ), mock.patch.object( target=chip_tool, attribute="start_chip_server" ): - await chip_tool.start_container(test_type) + await chip_tool.start_server(test_type) chip_tool.destroy_device() chip_tool.destroy_device() @@ -452,7 +452,7 @@ async def test_set_pics() -> None: target="app.chip_tool.chip_tool.subprocess.run", return_value=CompletedProcess(expected_command, 0), ) as mock_run: - await chip_tool.start_container(test_type) + await chip_tool.start_server(test_type) chip_tool.set_pics(pics) @@ -505,7 +505,7 @@ async def test_send_command_default_prefix() -> None: target="app.chip_tool.chip_tool.exec_run_in_container", return_value=mock_result, ) as mock_exec_run: - await chip_tool.start_container(test_type) + await chip_tool.start_server(test_type) result = chip_tool.send_command(cmd, prefix=chip_tool_prefix) @@ -548,7 +548,7 @@ async def test_send_command_custom_prefix() -> None: target="app.chip_tool.chip_tool.exec_run_in_container", return_value=mock_result, ) as mock_exec_run: - await chip_tool.start_container(test_type) + await chip_tool.start_server(test_type) result = chip_tool.send_command(cmd, prefix=chip_tool_prefix) @@ -589,7 +589,7 @@ async def test_run_test_default_config() -> None: target="app.chip_tool.chip_tool.WebSocketRunner.run", return_value=True, ) as mock_run: - await chip_tool.start_container(test_type) + await chip_tool.start_server(test_type) await chip_tool.run_test( test_step_interface=TestRunnerHooks(), @@ -643,7 +643,7 @@ async def test_run_test_custom_timeout() -> None: target="app.chip_tool.chip_tool.WebSocketRunner.run", return_value=True, ) as mock_run: - await chip_tool.start_container(test_type) + await chip_tool.start_server(test_type) await chip_tool.run_test( test_step_interface=TestRunnerHooks(), @@ -691,7 +691,7 @@ async def test_run_test_with_custom_parameter() -> None: target="app.chip_tool.chip_tool.WebSocketRunner.run", return_value=True, ) as mock_run: - await chip_tool.start_container(test_type) + await chip_tool.start_server(test_type) await chip_tool.run_test( test_step_interface=TestRunnerHooks(), @@ -740,7 +740,7 @@ async def test_run_test_with_endpoint_parameter() -> None: target="app.chip_tool.chip_tool.WebSocketRunner.run", return_value=True, ) as mock_run: - await chip_tool.start_container(test_type) + await chip_tool.start_server(test_type) await chip_tool.run_test( test_step_interface=TestRunnerHooks(), @@ -788,7 +788,7 @@ async def test_run_test_with_nodeID_and_cluster_parameters() -> None: target="app.chip_tool.chip_tool.WebSocketRunner.run", return_value=True, ) as mock_run: - await chip_tool.start_container(test_type) + await chip_tool.start_server(test_type) await chip_tool.run_test( test_step_interface=TestRunnerHooks(), @@ -847,7 +847,7 @@ async def test_pairing_on_network_command_params() -> None: attribute="send_websocket_command", return_value='{"results": []}', ) as mock_send_websocket_command: - await chip_tool.start_container(test_type) + await chip_tool.start_server(test_type) # Send on-network pairing command result = await chip_tool.pairing_on_network( @@ -898,7 +898,7 @@ async def test_pairing_ble_wifi_command_params() -> None: attribute="send_websocket_command", return_value='{"results": []}', ) as mock_send_websocket_command: - await chip_tool.start_container(test_type) + await chip_tool.start_server(test_type) # Send BLE-WIFI pairing command result = await chip_tool.pairing_ble_wifi( @@ -953,7 +953,7 @@ async def test_pairing_ble_thread_command_params() -> None: return_value='{"results": []}', # '{ "results": [{ "error": "FAILURE" }] ) as mock_send_websocket_command: - await chip_tool.start_container(test_type) + await chip_tool.start_server(test_type) # Send BLE-THREAD pairing command result = await chip_tool.pairing_ble_thread( @@ -973,3 +973,4 @@ async def test_pairing_ble_thread_command_params() -> None: # clean up: chip_tool._ChipTool__chip_tool_container = None settings.CHIP_TOOL_TRACE = original_trace_setting_value + diff --git a/test_collections/sdk_tests/support/python_testing/models/rpc_client/test_harness_client.py b/test_collections/sdk_tests/support/python_testing/models/rpc_client/test_harness_client.py new file mode 100644 index 00000000..415083cf --- /dev/null +++ b/test_collections/sdk_tests/support/python_testing/models/rpc_client/test_harness_client.py @@ -0,0 +1,71 @@ +# +# Copyright (c) 2023 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 importlib +import sys +from multiprocessing.managers import BaseManager + +from matter_testing_support import MatterTestConfig, run_tests + +try: + from matter_yamltests.hooks import TestRunnerHooks +except: + + class TestRunnerHooks: + pass + + +MATTER_DEVELOPMENT_PAA_ROOT_CERTS = "/paa-root-certs" + +# Pre-computed param list for each Python Test as defined in Verification Steps. +test_params = { + "TC_ACE_1_3": MatterTestConfig( + tests=["test_TC_ACE_1_3"], + commissioning_method="on-network", + discriminators=[3840], + setup_passcodes=[20202021], + dut_node_ids=[0x12344321], + paa_trust_store_path=MATTER_DEVELOPMENT_PAA_ROOT_CERTS, + storage_path="/root/admin_storage.json", + ) +} + + +def main(): + if len(sys.argv) != 2: + raise Exception("Python test id should be provided as the only parameter.") + + test_name = sys.argv[1] + + config = test_params.get(test_name) + + if config is None: + raise ValueError(f"Not a valid test id: {test_name}") + + module = importlib.import_module(test_name) + TestClassReference = getattr(module, test_name) + + BaseManager.register(TestRunnerHooks.__name__) + manager = BaseManager(address=("0.0.0.0", 50000), authkey=b"abc") + manager.connect() + test_runner_hooks = manager.TestRunnerHooks() # shared object proxy + + run_tests(TestClassReference, config, test_runner_hooks) + + +if __name__ == "__main__": + main() diff --git a/test_collections/sdk_tests/support/python_testing/models/test_case.py b/test_collections/sdk_tests/support/python_testing/models/test_case.py index 1fba63a6..1ae9d886 100644 --- a/test_collections/sdk_tests/support/python_testing/models/test_case.py +++ b/test_collections/sdk_tests/support/python_testing/models/test_case.py @@ -38,8 +38,8 @@ T = TypeVar("T", bound="PythonTestCase") # Command line params -RUNNER_CLASS = "external_runner.py" -RUNNER_CLASS_PATH = "./testing2/" +RUNNER_CLASS = "test_harness_client.py" +RUNNER_CLASS_PATH = "/root/python_testing/rpc_client" EXECUTABLE = "python3" @@ -161,7 +161,7 @@ async def setup(self) -> None: try: await super().setup() self.chip_tool = ChipTool() - await self.chip_tool.start_container_no_server() + await self.chip_tool.start_container() assert self.chip_tool.is_running() except NotImplementedError: pass @@ -225,7 +225,7 @@ async def execute(self) -> None: manager.start() test_runner_hooks = manager.TestRunnerHooks() # type: ignore runner_class = RUNNER_CLASS_PATH + RUNNER_CLASS - # TODO Ignoring stream from docker execution until client impl is done + # TODO Ignoring stream from docker execution self.chip_tool.send_command( f"{runner_class} {self.metadata['title']}", prefix=EXECUTABLE, @@ -239,26 +239,23 @@ async def execute(self) -> None: if not update: continue - def handle_update(update: dict) -> None: - def call_function(obj, func_name, kwargs) -> None: # type: ignore - func = getattr(obj, func_name, None) - if not func: - raise AttributeError( - f"{func_name} is not a method of {obj}" - ) - if not callable(func): - raise TypeError(f"{func_name} is not callable") - # Call the method with the unpacked keyword arguments. - func(**kwargs) - - for func_name, kwargs in update.items(): - call_function(self, func_name, kwargs) - - handle_update(update) + self.__handle_update(update) await sleep(0.1) finally: pass + def __handle_update(self, update: dict) -> None: + for func_name, kwargs in update.items(): + self.__call_function_from_name(self, func_name, kwargs) + + def __call_function_from_name(self, obj, func_name, kwargs) -> None: # type: ignore + func = getattr(obj, func_name, None) + if not func: + raise AttributeError(f"{func_name} is not a method of {obj}") + if not callable(func): + raise TypeError(f"{func_name} is not callable") + func(**kwargs) + async def cleanup(self) -> None: logger.info("Test Cleanup") self.chip_tool.destroy_device()