Sharing my local test runner script for `CI-like test app build and execution` (#35625)
* Add a test runner for convenient python runs
* Add some documentation
* Restyle
* Update requirements
* Use local build platforms, to maybe be able to run under darwin
* Use local build platforms, to maybe be able to run under darwin
* Fix typos
* More typo fixes
* Restyle
* Make platform name actually work
* Restyled by prettier-markdown
* Fix linter
* Add license
---------
Co-authored-by: Andrei Litvin <andreilitvin@google.com>
Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/docs/testing/python.md b/docs/testing/python.md
index ec358b8..c631550 100644
--- a/docs/testing/python.md
+++ b/docs/testing/python.md
@@ -637,6 +637,18 @@
Ex:
`scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_ICDM_2_1.py'`
+## Running ALL or a subset of tests when changing application code
+
+`scripts/tests/local.py` is a wrapper that is able to build and run tests in a
+single command.
+
+Example to compile all prerequisites and then running all python tests:
+
+```
+./scripts/tests/local.py build # will compile python in out/pyenv and ALL application prerequisites
+./scripts/tests/local.py python-tests # Runs all python tests that are runnable in CI
+```
+
## Defining the CI test arguments
Below is the format of the structured environment definition comments:
diff --git a/scripts/tests/local.py b/scripts/tests/local.py
new file mode 100755
index 0000000..f44bdc2
--- /dev/null
+++ b/scripts/tests/local.py
@@ -0,0 +1,786 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2024 Project CHIP Authors
+#
+# 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 enum
+import fnmatch
+import glob
+import logging
+import os
+import platform
+import shlex
+import stat
+import subprocess
+import sys
+import textwrap
+import time
+from dataclasses import dataclass
+from typing import List
+
+import alive_progress
+import click
+import coloredlogs
+import tabulate
+
+# We compile for the local architecture. Figure out what platform we need
+
+
+def _get_native_machine_target():
+ """
+ Returns the build prefix for applications, such as 'linux-x64'.
+ """
+ current_system_info = platform.uname()
+ arch = current_system_info.machine
+ if arch == 'x86_64':
+ arch = 'x64'
+ elif arch == 'i386' or arch == 'i686':
+ arch = 'x86'
+ elif arch in ('aarch64', 'aarch64_be', 'armv8b', 'armv8l'):
+ arch = 'arm64'
+
+ return f"{current_system_info.system.lower()}-{arch}"
+
+
+class BinaryRunner(enum.Enum):
+ """
+ Enumeration describing a wrapper runner for an application. Useful for debugging
+ failures (i.e. running under memory validators or replayability for failures).
+ """
+
+ NONE = enum.auto()
+ RR = enum.auto()
+ VALGRIND = enum.auto()
+
+ def execute_str(self, path: str):
+ if self == BinaryRunner.NONE:
+ return path
+ elif self == BinaryRunner.RR:
+ return f"rr record {path}"
+ elif self == BinaryRunner.VALGRIND:
+ return f"valgrind {path}"
+
+
+__RUNNERS__ = {
+ "none": BinaryRunner.NONE,
+ "rr": BinaryRunner.RR,
+ "valgrind": BinaryRunner.VALGRIND,
+}
+
+__LOG_LEVELS__ = {
+ "debug": logging.DEBUG,
+ "info": logging.INFO,
+ "warn": logging.WARN,
+ "fatal": logging.FATAL,
+}
+
+
+@dataclass
+class ExecutionTimeInfo:
+ """
+ Contains information about duration that a script took to run
+ """
+
+ script: str
+ duration_sec: float
+
+
+# Top level command, groups all other commands for the purpose of having
+# common command line arguments.
+@click.group()
+@click.option(
+ "--log-level",
+ default="INFO",
+ type=click.Choice(list(__LOG_LEVELS__.keys()), case_sensitive=False),
+ help="Determines the verbosity of script output",
+)
+def cli(log_level):
+ """
+ Helper script design to make running tests localy simpler. Handles
+ application/prerequisite builds and execution of tests.
+
+ The binary is designed to be run in the root checkout and will
+ compile things in `out/` and execute tests locally.
+
+ These are examples for running "Python tests"
+
+ \b
+ local.py build # builds python and applications
+ local.py python-tests # Runs ALL python tests
+ local.py python-tests --test-filter TC_FAN # Runs all *FAN* tests
+
+ \b
+ local.py build-apps # Re-build applications (if only those changed)
+ local.py build-python # Re-build python module only
+ """
+ coloredlogs.install(
+ level=__LOG_LEVELS__[log_level], fmt="%(asctime)s %(levelname)-7s %(message)s"
+ )
+
+
+def _with_activate(build_cmd: List[str]) -> List[str]:
+ """
+ Given a bash command list, will generate a new command suitable for subprocess
+ with an execution of `scripts/activate.sh` prepended to it
+ """
+ return [
+ "bash",
+ "-c",
+ ";".join(["set -e", "source scripts/activate.sh", shlex.join(build_cmd)]),
+ ]
+
+
+def _do_build_python():
+ """
+ Builds a python virtual environment into `out/venv`
+ """
+ logging.info("Building python packages in out/venv ...")
+ subprocess.run(
+ ["./scripts/build_python.sh", "--install_virtual_env", "out/venv"], check=True
+ )
+
+
+def _do_build_apps():
+ """
+ Builds example python apps suitable for running all python_tests.
+
+ This builds a LOT of apps (significant storage usage).
+ """
+ logging.info("Building example apps...")
+
+ target_prefix = _get_native_machine_target()
+ targets = [
+ f"{target_prefix}-chip-tool-no-ble-clang-boringssl",
+ f"{target_prefix}-all-clusters-no-ble-clang-boringssl",
+ f"{target_prefix}-bridge-no-ble-clang-boringssl",
+ f"{target_prefix}-energy-management-no-ble-clang-boringssl",
+ f"{target_prefix}-lit-icd-no-ble-clang-boringssl",
+ f"{target_prefix}-lock-no-ble-clang-boringssl",
+ f"{target_prefix}-microwave-oven-no-ble-clang-boringssl",
+ f"{target_prefix}-ota-provider-no-ble-clang-boringssl",
+ f"{target_prefix}-ota-requestor-no-ble-clang-boringssl",
+ f"{target_prefix}-rvc-no-ble-clang-boringssl",
+ f"{target_prefix}-tv-app-no-ble-clang-boringssl",
+ f"{target_prefix}-network-manager-ipv6only-no-ble-clang-boringssl",
+ ]
+
+ cmd = ["./scripts/build/build_examples.py"]
+ for target in targets:
+ cmd.append("--target")
+ cmd.append(target)
+ cmd.append("build")
+
+ subprocess.run(_with_activate(cmd), check=True)
+
+
+def _do_build_basic_apps():
+ """
+ Builds a minimal subset of test applications, specifically
+ all-clusters and chip-tool only, for basic tests.
+ """
+ logging.info("Building example apps...")
+ target_prefix = _get_native_machine_target()
+ targets = [
+ f"{target_prefix}-chip-tool-no-ble-clang-boringssl",
+ f"{target_prefix}-all-clusters-no-ble-clang-boringssl",
+ ]
+
+ cmd = ["./scripts/build/build_examples.py"]
+ for target in targets:
+ cmd.append("--target")
+ cmd.append(target)
+ cmd.append("build")
+
+ subprocess.run(_with_activate(cmd), check=True)
+
+
+@cli.command()
+def build_basic_apps():
+ """Builds chip-tool and all-clusters app."""
+ _do_build_basic_apps()
+
+
+@cli.command()
+def build_python():
+ """
+ Builds a python environment in out/pyenv.
+
+ Generally used together with `python-tests`.
+ To re-build the python environment use `build-apps`.
+ To re-build both python and apps, use `build`
+ """
+ _do_build_python()
+
+
+@cli.command()
+def build_apps():
+ """
+ Builds MANY apps used by python-tests.
+
+ Generally used together with `python-tests`.
+ To re-build the python environment use `build-python`.
+ To re-build both python and apps, use `build`
+ """
+ _do_build_apps()
+
+
+@cli.command()
+def build():
+ """
+ Builds both python and apps (same as build-python + build-apps)
+
+ Generally used together with `python-tests`.
+ """
+ _do_build_python()
+ _do_build_apps()
+
+
+def _maybe_with_runner(script_name: str, path: str, runner: BinaryRunner):
+ """
+ Constructs a "real" path to execute, which may be replacing the input
+ path with a wrapper script that executes things like valgrind or rr.
+ """
+ if runner == BinaryRunner.NONE:
+ return path
+
+ # create a separate runner script based on the app
+ script_name = f"out/{script_name}.sh"
+ with open(script_name, "wt") as f:
+ f.write(
+ textwrap.dedent(
+ f"""\
+ #!/usr/bin/env bash
+
+ {runner.execute_str(path)}
+ """
+ )
+ )
+ st = os.stat(script_name)
+ os.chmod(script_name, st.st_mode | stat.S_IEXEC)
+
+ return script_name
+
+
+def _add_target_to_cmd(cmd, flag, path, runner):
+ """
+ Handles the `--target` argument (or similar) to a command list.
+
+ Specifically it figures out how to convert `path` into either itself or
+ execution via a `runner` script.
+
+ cmd will get "<flag> <executable>" appended to it, where executable
+ is either the input path or a wrapper script to execute via the given
+ input runner.
+ """
+ cmd.append(flag)
+ cmd.append(_maybe_with_runner(flag[2:].replace("-", "_"), path, runner))
+
+
+@cli.command()
+@click.option(
+ "--test-filter",
+ default="*",
+ show_default=True,
+ help="Run only tests that match the given glob filter.",
+)
+@click.option(
+ "--from-filter",
+ default=None,
+ help="Start running from the test matching the given glob pattern (including the test that matches).",
+)
+@click.option(
+ "--from-skip-filter",
+ default=None,
+ help="Start from the first test matching the given glob, but skip the matching element (start right after).",
+)
+@click.option(
+ "--dry-run",
+ default=False,
+ is_flag=True,
+ show_default=True,
+ help="Don't actually execute the tests, just print out the command that would be run.",
+)
+@click.option(
+ "--show_timings",
+ default=False,
+ is_flag=True,
+ show_default=True,
+ help="At the end of the execution, show how many seconds each test took.",
+)
+@click.option(
+ "--runner",
+ default="none",
+ type=click.Choice(list(__RUNNERS__.keys()), case_sensitive=False),
+ help="Determines the verbosity of script output",
+)
+def python_tests(
+ test_filter, from_filter, from_skip_filter, dry_run, show_timings, runner
+):
+ """
+ Run python tests via `run_python_test.py`
+
+ Constructs the run yaml in `out/test_env.yaml`. Assumes that binaries
+ were built already, generally with `build` (or separate `build-python` and `build-apps`).
+ """
+ runner = __RUNNERS__[runner]
+
+ def as_runner(path):
+ return _maybe_with_runner(os.path.basename(path), path, runner)
+
+ # create an env file
+ target_prefix = _get_native_machine_target()
+ with open("out/test_env.yaml", "wt") as f:
+ f.write(
+ textwrap.dedent(
+ f"""\
+ ALL_CLUSTERS_APP: {as_runner(f'out/{target_prefix}-all-clusters-no-ble-clang-boringssl/chip-all-clusters-app')}
+ CHIP_LOCK_APP: {as_runner(f'out/{target_prefix}-lock-no-ble-clang-boringssl/chip-lock-app')}
+ ENERGY_MANAGEMENT_APP: {
+ as_runner(f'out/{target_prefix}-energy-management-no-ble-clang-boringssl/chip-energy-management-app')}
+ LIT_ICD_APP: {as_runner(f'out/{target_prefix}-lit-icd-no-ble-clang-boringssl/lit-icd-app')}
+ CHIP_MICROWAVE_OVEN_APP: {
+ as_runner(f'out/{target_prefix}-microwave-oven-no-ble-clang-boringssl/chip-microwave-oven-app')}
+ CHIP_RVC_APP: {as_runner(f'out/{target_prefix}-rvc-no-ble-clang-boringssl/chip-rvc-app')}
+ NETWORK_MANAGEMENT_APP: {
+ as_runner(f'out/{target_prefix}-network-manager-ipv6only-no-ble-clang-boringssl/matter-network-manager-app')}
+ TRACE_APP: out/trace_data/app-{{SCRIPT_BASE_NAME}}
+ TRACE_TEST_JSON: out/trace_data/test-{{SCRIPT_BASE_NAME}}
+ TRACE_TEST_PERFETTO: out/trace_data/test-{{SCRIPT_BASE_NAME}}
+ """
+ )
+ )
+
+ if not test_filter.startswith("*"):
+ test_filter = "*" + test_filter
+ if not test_filter.endswith("*"):
+ test_filter = test_filter + "*"
+
+ if from_filter:
+ if not from_filter.startswith("*"):
+ from_filter = "*" + from_filter
+ if not from_filter.endswith("*"):
+ from_filter = from_filter + "*"
+
+ if from_skip_filter:
+ if not from_skip_filter.startswith("*"):
+ from_skip_filter = "*" + from_skip_filter
+ if not from_skip_filter.endswith("*"):
+ from_skip_filter = from_skip_filter + "*"
+
+ # This MUST be available or perfetto dies. This is VERY annoying to debug
+ if not os.path.exists("out/trace_data"):
+ os.mkdir("out/trace_data")
+
+ # IGNORES are taken out of `src/python_testing/execute_python_tests.py` in the SDK
+ excluded_patterns = {
+ "MinimalRepresentation.py",
+ "TC_CNET_4_4.py",
+ "TC_CCTRL_2_1.py",
+ "TC_CCTRL_2_2.py",
+ "TC_CCTRL_2_3.py",
+ "TC_DGGEN_3_2.py",
+ "TC_EEVSE_Utils.py",
+ "TC_ECOINFO_2_1.py",
+ "TC_ECOINFO_2_2.py",
+ "TC_EWATERHTRBase.py",
+ "TC_EWATERHTR_2_1.py",
+ "TC_EWATERHTR_2_2.py",
+ "TC_EWATERHTR_2_3.py",
+ "TC_EnergyReporting_Utils.py",
+ "TC_OpstateCommon.py",
+ "TC_pics_checker.py",
+ "TC_TMP_2_1.py",
+ "TC_MCORE_FS_1_1.py",
+ "TC_MCORE_FS_1_2.py",
+ "TC_MCORE_FS_1_3.py",
+ "TC_MCORE_FS_1_4.py",
+ "TC_MCORE_FS_1_5.py",
+ "TC_OCC_3_1.py",
+ "TC_OCC_3_2.py",
+ "TC_BRBINFO_4_1.py",
+ "TestCommissioningTimeSync.py",
+ "TestConformanceSupport.py",
+ "TestChoiceConformanceSupport.py",
+ "TC_DEMTestBase.py",
+ "choice_conformance_support.py",
+ "TestConformanceTest.py", # Unit test of the conformance test (TC_DeviceConformance) - does not run against an app.
+ "TestIdChecks.py",
+ "TestSpecParsingDeviceType.py",
+ "TestMatterTestingSupport.py",
+ "TestSpecParsingSupport.py",
+ "TestTimeSyncTrustedTimeSource.py",
+ "basic_composition_support.py",
+ "conformance_support.py",
+ "drlk_2_x_common.py",
+ "execute_python_tests.py",
+ "global_attribute_ids.py",
+ "hello_external_runner.py",
+ "hello_test.py",
+ "matter_testing_support.py",
+ "pics_support.py",
+ "spec_parsing_support.py",
+ "taglist_and_topology_test_support.py",
+ "test_plan_support.py",
+ "test_plan_table_generator.py",
+ }
+
+ if not os.path.isdir("src/python_testing"):
+ raise Exception(
+ "Script meant to be run from the CHIP checkout root (src/python_testing must exist)."
+ )
+
+ test_scripts = []
+ for file in glob.glob(os.path.join("src/python_testing/", "*.py")):
+ if os.path.basename(file) in excluded_patterns:
+ continue
+ test_scripts.append(file)
+ test_scripts.append("src/controller/python/test/test_scripts/mobile-device-test.py")
+ test_scripts.sort() # order consistent
+
+ # NOTE: VERY slow tests. we add logs to not get impatient
+ slow_test_duration = {
+ "mobile-device-test.py": "3 minutes",
+ "TC_AccessChecker.py": "1.5 minutes",
+ "TC_CADMIN_1_9.py": "40 seconds",
+ "TC_CC_2_2.py": "1.5 minutes",
+ "TC_DEM_2_10.py": "40 seconds",
+ "TC_DeviceBasicComposition.py": "25 seconds",
+ "TC_DRLK_2_12.py": "30 seconds",
+ "TC_DRLK_2_3.py": "30 seconds",
+ "TC_EEVSE_2_6.py": "30 seconds",
+ "TC_FAN_3_1.py": "15 seconds",
+ "TC_OPSTATE_2_5.py": "1.25 minutes",
+ "TC_OPSTATE_2_6.py": "35 seconds",
+ "TC_PS_2_3.py": "30 seconds",
+ "TC_RR_1_1.py": "25 seconds",
+ "TC_SWTCH.py": "1 minute",
+ "TC_TIMESYNC_2_10.py": "20 seconds",
+ "TC_TIMESYNC_2_11.py": "30 seconds",
+ "TC_TIMESYNC_2_12.py": "20 seconds",
+ "TC_TIMESYNC_2_7.py": "20 seconds",
+ "TC_TIMESYNC_2_8.py": "1.5 minutes",
+ "TC_ICDM_5_1.py": "TODO",
+ }
+
+ execution_times = []
+ try:
+ to_run = []
+ for script in fnmatch.filter(test_scripts, test_filter or "*.*"):
+ if from_filter:
+ if not fnmatch.fnmatch(script, from_filter):
+ logging.info("From-filter SKIP %s", script)
+ continue
+ from_filter = None
+
+ if from_skip_filter:
+ if fnmatch.fnmatch(script, from_skip_filter):
+ from_skip_filter = None
+ logging.info("From-skip-filter SKIP %s", script)
+ continue
+ to_run.append(script)
+
+ with alive_progress.alive_bar(len(to_run), title="Running tests") as bar:
+ for script in to_run:
+ bar.text(script)
+ cmd = [
+ "scripts/run_in_python_env.sh",
+ "out/venv",
+ f"./scripts/tests/run_python_test.py --load-from-env out/test_env.yaml --script {script}",
+ ]
+
+ if dry_run:
+ print(shlex.join(cmd))
+ continue
+
+ base_name = os.path.basename(script)
+ if base_name in slow_test_duration:
+ logging.warning(
+ "SLOW test '%s' is executing (expect to take around %s). Be patient...",
+ base_name,
+ slow_test_duration[base_name],
+ )
+ elif base_name == "TC_EEVSE_2_3.py":
+ # TODO: this should be fixed ...
+ # for now just note that a `TZ=UTC` makes this pass
+ logging.warning(
+ "Test %s is TIMEZONE dependent. Passes with UTC but fails on EST. If this fails set 'TZ=UTC' for running the test.",
+ base_name,
+ )
+
+ tstart = time.time()
+ result = subprocess.run(cmd, capture_output=True)
+ tend = time.time()
+
+ if result.returncode != 0:
+ logging.error("Test failed: %s", script)
+ logging.info("STDOUT:\n%s", result.stdout.decode("utf8"))
+ logging.warning("STDERR:\n%s", result.stderr.decode("utf8"))
+ sys.exit(1)
+
+ time_info = ExecutionTimeInfo(
+ script=base_name, duration_sec=(tend - tstart)
+ )
+ execution_times.append(time_info)
+
+ if time_info.duration_sec > 20 and base_name not in slow_test_duration:
+ logging.warning(
+ "%s finished in %0.2f seconds",
+ time_info.script,
+ time_info.duration_sec,
+ )
+ bar()
+ finally:
+ if execution_times and show_timings:
+ execution_times.sort(key=lambda v: v.duration_sec)
+ print(
+ tabulate.tabulate(execution_times, headers=["Script", "Duration(sec)"])
+ )
+
+
+def _do_build_fabric_sync_apps():
+ """
+ Build applications used for fabric sync tests
+ """
+ target_prefix = _get_native_machine_target()
+ targets = [
+ f"{target_prefix}-fabric-bridge-boringssl-rpc-no-ble",
+ f"{target_prefix}-fabric-admin-boringssl-rpc",
+ f"{target_prefix}-all-clusters-boringssl-no-ble",
+ ]
+
+ build_cmd = ["./scripts/build/build_examples.py"]
+ for target in targets:
+ build_cmd.append("--target")
+ build_cmd.append(target)
+ build_cmd.append("build")
+
+ subprocess.run(_with_activate(build_cmd))
+
+
+@cli.command()
+def build_fabric_sync_apps():
+ """
+ Build fabric synchronizatio applications.
+ """
+ _do_build_fabric_sync_apps()
+
+
+@cli.command()
+def build_fabric_sync():
+ """
+ Builds both python environment and fabric sync applications
+ """
+ # fabric sync interfaces with python for convenience, so do that
+ _do_build_python()
+ _do_build_fabric_sync_apps()
+
+
+@cli.command()
+@click.option(
+ "--data-model-interface", type=click.Choice(["enabled", "disabled", "check"])
+)
+@click.option("--asan", is_flag=True, default=False, show_default=True)
+def build_casting_apps(data_model_interface, asan):
+ """
+ Builds Applications used for tv casting tests
+ """
+ tv_args = []
+ casting_args = []
+
+ casting_args.append("chip_casting_simplified=true")
+
+ tv_args.append('chip_crypto="boringssl"')
+ casting_args.append('chip_crypto="boringssl"')
+
+ if data_model_interface:
+ tv_args.append(f'chip_use_data_model_interface="{data_model_interface}"')
+ casting_args.append(f'chip_use_data_model_interface="{data_model_interface}"')
+
+ if asan:
+ tv_args.append("is_asan=true is_clang=true")
+ casting_args.append("is_asan=true is_clang=true")
+
+ tv_args = " ".join(tv_args)
+ casting_args = " ".join(casting_args)
+
+ if tv_args:
+ tv_args = f" '{tv_args}'"
+ if casting_args:
+ casting_args = f" '{casting_args}'"
+
+ cmd = ";".join(
+ [
+ "set -e",
+ "source scripts/activate.sh",
+ f"./scripts/examples/gn_build_example.sh examples/tv-app/linux/ out/tv-app{tv_args}",
+ f"./scripts/examples/gn_build_example.sh examples/tv-casting-app/linux/ out/tv-casting-app{casting_args}",
+ ]
+ )
+ subprocess.run(["bash", "-c", cmd], check=True)
+
+
+@cli.command()
+@click.option("--test", type=click.Choice(["basic", "passcode"]), default="basic")
+@click.option("--log-directory", default=None)
+@click.option(
+ "--tv-app",
+ type=str,
+ default="out/tv-app/chip-tv-app",
+)
+@click.option(
+ "--tv-casting-app",
+ type=str,
+ default="out/tv-casting-app/chip-tv-casting-app",
+)
+@click.option(
+ "--runner",
+ default="none",
+ type=click.Choice(list(__RUNNERS__.keys()), case_sensitive=False),
+ help="Determines the verbosity of script output",
+)
+def casting_test(test, log_directory, tv_app, tv_casting_app, runner):
+ """
+ Runs the tv casting tests.
+
+ Generally used after `build-casting-apps`.
+ """
+ runner = __RUNNERS__[runner]
+
+ script = "python3 scripts/tests/run_tv_casting_test.py"
+
+ script += f" --tv-app-rel-path '{_maybe_with_runner('tv_app', tv_app, runner)}'"
+ script += f" --tv-casting-app-rel-path '{_maybe_with_runner('casting_app', tv_casting_app, runner)}'"
+
+ if test == "passcode":
+ script += " --commissioner-generated-passcode true"
+
+ if log_directory:
+ script += f" --log-directory '{log_directory}'"
+
+ cmd = ";".join(["set -e", "source scripts/activate.sh", script])
+ subprocess.run(["bash", "-c", cmd], check=True)
+
+
+@cli.command()
+@click.option("--target", default=None)
+@click.option("--target-glob", default=None)
+@click.option("--include-tags", default=None)
+@click.option("--expected-failures", default=None)
+@click.option(
+ "--runner",
+ default="none",
+ type=click.Choice(list(__RUNNERS__.keys()), case_sensitive=False),
+ help="Determines the verbosity of script output",
+)
+def chip_tool_tests(target, target_glob, include_tags, expected_failures, runner):
+ """
+ Run integration tests using chip-tool.
+
+ Assumes `build-apps` was used to build applications, although build-basic-apps will be
+ sufficient for all-clusters tests.
+ """
+
+ # This likely should be run in docker to not allow breaking things
+ # run as:
+ #
+ # docker run --rm -it -v ~/devel/connectedhomeip:/workspace --privileged ghcr.io/project-chip/chip-build-vscode:64
+ runner = __RUNNERS__[runner]
+
+ cmd = [
+ "./scripts/tests/run_test_suite.py",
+ "--runner",
+ "chip_tool_python",
+ ]
+
+ target_prefix = _get_native_machine_target()
+ cmd.extend(
+ ["--chip-tool", f"./out/{target_prefix}-chip-tool-no-ble-clang-boringssl/chip-tool"]
+ )
+
+ if target is not None:
+ cmd.extend(["--target", target])
+
+ if include_tags is not None:
+ cmd.extend(["--include-tags", include_tags])
+
+ if target_glob is not None:
+ cmd.extend(["--target-glob", target_glob])
+
+ cmd.append("run")
+ cmd.extend(["--iterations", "1"])
+ cmd.extend(["--test-timeout-seconds", "60"])
+
+ if expected_failures is not None:
+ cmd.extend(["--expected-failures", expected_failures, "--keep-going"])
+
+ _add_target_to_cmd(
+ cmd,
+ "--all-clusters-app",
+ f"./out/{target_prefix}-all-clusters-no-ble-clang-boringssl/chip-all-clusters-app",
+ runner,
+ )
+ _add_target_to_cmd(
+ cmd,
+ "--lock-app",
+ f"./out/{target_prefix}-lock-no-ble-clang-boringssl/chip-lock-app",
+ runner,
+ )
+ _add_target_to_cmd(
+ cmd,
+ "--ota-provider-app",
+ f"./out/{target_prefix}-ota-provider-no-ble-clang-boringssl/chip-ota-provider-app",
+ runner,
+ )
+ _add_target_to_cmd(
+ cmd,
+ "--ota-requestor-app",
+ f"./out/{target_prefix}-ota-requestor-no-ble-clang-boringssl/chip-ota-requestor-app",
+ runner,
+ )
+ _add_target_to_cmd(
+ cmd,
+ "--tv-app",
+ f"./out/{target_prefix}-tv-app-no-ble-clang-boringssl/chip-tv-app",
+ runner,
+ )
+ _add_target_to_cmd(
+ cmd,
+ "--bridge-app",
+ f"./out/{target_prefix}-bridge-no-ble-clang-boringssl/chip-bridge-app",
+ runner,
+ )
+ _add_target_to_cmd(
+ cmd,
+ "--lit-icd-app",
+ f"./out/{target_prefix}-lit-icd-no-ble-clang-boringssl/lit-icd-app",
+ runner,
+ )
+ _add_target_to_cmd(
+ cmd,
+ "--microwave-oven-app",
+ f"./out/{target_prefix}-microwave-oven-no-ble-clang-boringssl/chip-microwave-oven-app",
+ runner,
+ )
+ _add_target_to_cmd(
+ cmd,
+ "--rvc-app",
+ f"./out/{target_prefix}-rvc-no-ble-clang-boringssl/chip-rvc-app",
+ runner,
+ )
+
+ subprocess.run(_with_activate(cmd), check=True)
+
+
+if __name__ == "__main__":
+ cli()
diff --git a/scripts/tests/requirements.txt b/scripts/tests/requirements.txt
index f942e0f..9e2d857 100644
--- a/scripts/tests/requirements.txt
+++ b/scripts/tests/requirements.txt
@@ -1,6 +1,9 @@
# Python requirements for scripts in this location
+alive_progress
click
colorama
+coloredlogs
diskcache
+mypy==1.10.1
+tabulate
websockets
-mypy==1.10.1
\ No newline at end of file