From 2489c14650c32f9bb61fb27adebcc591591b750a Mon Sep 17 00:00:00 2001
From: Andrei Litvin <andreilitvin@google.com>
Date: Tue, 17 Sep 2024 10:44:08 -0400
Subject: [PATCH 01/13] Add a test runner for convenient python runs

---
 scripts/tests/local.py         | 746 +++++++++++++++++++++++++++++++++
 scripts/tests/requirements.txt |   4 +-
 2 files changed, 749 insertions(+), 1 deletion(-)
 create mode 100755 scripts/tests/local.py

diff --git a/scripts/tests/local.py b/scripts/tests/local.py
new file mode 100755
index 00000000000000..d99f54a1139bca
--- /dev/null
+++ b/scripts/tests/local.py
@@ -0,0 +1,746 @@
+#!/usr/bin/env python3
+
+import fnmatch
+import glob
+import logging
+import os
+import shlex
+import stat
+import subprocess
+import sys
+import tabulate
+import textwrap
+import time
+
+import click
+import coloredlogs
+
+from typing import List
+from dataclasses import dataclass
+
+import enum
+import alive_progress
+
+
+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...")
+    targets = [
+        "linux-x64-chip-tool-no-ble-clang-boringssl",
+        "linux-x64-all-clusters-no-ble-clang-boringssl",
+        "linux-x64-bridge-no-ble-clang-boringssl",
+        "linux-x64-energy-management-no-ble-clang-boringssl",
+        "linux-x64-lit-icd-no-ble-clang-boringssl",
+        "linux-x64-lock-no-ble-clang-boringssl",
+        "linux-x64-microwave-oven-no-ble-clang-boringssl",
+        "linux-x64-ota-provider-no-ble-clang-boringssl",
+        "linux-x64-ota-requestor-no-ble-clang-boringssl",
+        "linux-x64-rvc-no-ble-clang-boringssl",
+        "linux-x64-tv-app-no-ble-clang-boringssl",
+        "linux-x64-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...")
+    targets = [
+        "linux-x64-chip-tool-no-ble-clang-boringssl",
+        "linux-x64-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
+    with open("out/test_env.yaml", "wt") as f:
+        f.write(
+            textwrap.dedent(
+                f"""\
+            ALL_CLUSTERS_APP: {as_runner('out/linux-x64-all-clusters-no-ble-clang-boringssl/chip-all-clusters-app')}
+            CHIP_LOCK_APP: {as_runner('out/linux-x64-lock-no-ble-clang-boringssl/chip-lock-app')}
+            ENERGY_MANAGEMENT_APP: {as_runner('out/linux-x64-energy-management-no-ble-clang-boringssl/chip-energy-management-app')}
+            LIT_ICD_APP: {as_runner('out/linux-x64-lit-icd-no-ble-clang-boringssl/lit-icd-app')}
+            CHIP_MICROWAVE_OVEN_APP: {as_runner('out/linux-x64-microwave-oven-no-ble-clang-boringssl/chip-microwave-oven-app')}
+            CHIP_RVC_APP: {as_runner('out/linux-x64-rvc-no-ble-clang-boringssl/chip-rvc-app')}
+            NETWORK_MANAGEMENT_APP: {as_runner('out/linux-x64-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_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",
+    }
+
+    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
+    """
+    targets = [
+        "linux-x64-fabric-bridge-boringssl-rpc-no-ble",
+        "linux-x64-fabric-admin-boringssl-rpc",
+        "linux-x64-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(f"is_asan=true is_clang=true")
+        casting_args.append(f"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",
+    ]
+
+    cmd.extend(
+        ["--chip-tool", "./out/linux-x64-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",
+        "./out/linux-x64-all-clusters-no-ble-clang-boringssl/chip-all-clusters-app",
+        runner,
+    )
+    _add_target_to_cmd(
+        cmd,
+        "--lock-app",
+        "./out/linux-x64-lock-no-ble-clang-boringssl/chip-lock-app",
+        runner,
+    )
+    _add_target_to_cmd(
+        cmd,
+        "--ota-provider-app",
+        "./out/linux-x64-ota-provider-no-ble-clang-boringssl/chip-ota-provider-app",
+        runner,
+    )
+    _add_target_to_cmd(
+        cmd,
+        "--ota-requestor-app",
+        "./out/linux-x64-ota-requestor-no-ble-clang-boringssl/chip-ota-requestor-app",
+        runner,
+    )
+    _add_target_to_cmd(
+        cmd,
+        "--tv-app",
+        "./out/linux-x64-tv-app-no-ble-clang-boringssl/chip-tv-app",
+        runner,
+    )
+    _add_target_to_cmd(
+        cmd,
+        "--bridge-app",
+        "./out/linux-x64-bridge-no-ble-clang-boringssl/chip-bridge-app",
+        runner,
+    )
+    _add_target_to_cmd(
+        cmd,
+        "--lit-icd-app",
+        "./out/linux-x64-lit-icd-no-ble-clang-boringssl/lit-icd-app",
+        runner,
+    )
+    _add_target_to_cmd(
+        cmd,
+        "--microwave-oven-app",
+        "./out/linux-x64-microwave-oven-no-ble-clang-boringssl/chip-microwave-oven-app",
+        runner,
+    )
+    _add_target_to_cmd(
+        cmd,
+        "--rvc-app",
+        "./out/linux-x64-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 f942e0f542b63f..4d561f0a45ba00 100644
--- a/scripts/tests/requirements.txt
+++ b/scripts/tests/requirements.txt
@@ -3,4 +3,6 @@ click
 colorama
 diskcache
 websockets
-mypy==1.10.1
\ No newline at end of file
+mypy==1.10.1
+alive_progress
+coloredlogs

From 3c7a4a0984c8da5ccc79f65677b09103804c1a27 Mon Sep 17 00:00:00 2001
From: Andrei Litvin <andreilitvin@google.com>
Date: Tue, 17 Sep 2024 10:48:24 -0400
Subject: [PATCH 02/13] Add some documentation

---
 docs/testing/python.md | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/docs/testing/python.md b/docs/testing/python.md
index ec358b8796403e..b9aada78db08b3 100644
--- a/docs/testing/python.md
+++ b/docs/testing/python.md
@@ -637,6 +637,17 @@ format, use the `--load-from-env` flag with the `run_python_tests.py` runner.
 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:

From 83ece28a2ee5cd7e748186f6d054d31e5f891956 Mon Sep 17 00:00:00 2001
From: Andrei Litvin <andreilitvin@google.com>
Date: Tue, 17 Sep 2024 10:49:08 -0400
Subject: [PATCH 03/13] Restyle

---
 scripts/tests/local.py | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/scripts/tests/local.py b/scripts/tests/local.py
index d99f54a1139bca..31730bfa8d3309 100755
--- a/scripts/tests/local.py
+++ b/scripts/tests/local.py
@@ -1,5 +1,6 @@
 #!/usr/bin/env python3
 
+import enum
 import fnmatch
 import glob
 import logging
@@ -8,18 +9,15 @@
 import stat
 import subprocess
 import sys
-import tabulate
 import textwrap
 import time
-
-import click
-import coloredlogs
-
-from typing import List
 from dataclasses import dataclass
+from typing import List
 
-import enum
 import alive_progress
+import click
+import coloredlogs
+import tabulate
 
 
 class BinaryRunner(enum.Enum):

From b0a0400a4d3c80f2bb58a9ea1df1bbe83eb406bc Mon Sep 17 00:00:00 2001
From: Andrei Litvin <andreilitvin@google.com>
Date: Tue, 17 Sep 2024 10:50:03 -0400
Subject: [PATCH 04/13] Update requirements

---
 scripts/tests/requirements.txt | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/scripts/tests/requirements.txt b/scripts/tests/requirements.txt
index 4d561f0a45ba00..9e2d8570359155 100644
--- a/scripts/tests/requirements.txt
+++ b/scripts/tests/requirements.txt
@@ -1,8 +1,9 @@
 # Python requirements for scripts in this location
+alive_progress
 click
 colorama
+coloredlogs
 diskcache
-websockets
 mypy==1.10.1
-alive_progress
-coloredlogs
+tabulate
+websockets

From 787ede7fdc4fa07e346a3f2b06945c575e06d844 Mon Sep 17 00:00:00 2001
From: Andrei Litvin <andreilitvin@google.com>
Date: Tue, 17 Sep 2024 11:02:46 -0400
Subject: [PATCH 05/13] Use local build platforms, to maybe be able to run
 under darwin

---
 scripts/tests/local.py | 103 +++++++++++++++++++++++++----------------
 1 file changed, 64 insertions(+), 39 deletions(-)

diff --git a/scripts/tests/local.py b/scripts/tests/local.py
index 31730bfa8d3309..93c0b30b86f128 100755
--- a/scripts/tests/local.py
+++ b/scripts/tests/local.py
@@ -19,6 +19,21 @@
 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 = 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):
     """
@@ -125,19 +140,21 @@ def _do_build_apps():
     This builds a LOT of apps (significant storage usage).
     """
     logging.info("Building example apps...")
+
+    target_prefix = _get_native_machine_target()
     targets = [
-        "linux-x64-chip-tool-no-ble-clang-boringssl",
-        "linux-x64-all-clusters-no-ble-clang-boringssl",
-        "linux-x64-bridge-no-ble-clang-boringssl",
-        "linux-x64-energy-management-no-ble-clang-boringssl",
-        "linux-x64-lit-icd-no-ble-clang-boringssl",
-        "linux-x64-lock-no-ble-clang-boringssl",
-        "linux-x64-microwave-oven-no-ble-clang-boringssl",
-        "linux-x64-ota-provider-no-ble-clang-boringssl",
-        "linux-x64-ota-requestor-no-ble-clang-boringssl",
-        "linux-x64-rvc-no-ble-clang-boringssl",
-        "linux-x64-tv-app-no-ble-clang-boringssl",
-        "linux-x64-network-manager-ipv6only-no-ble-clang-boringssl",
+       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"]
@@ -155,9 +172,10 @@ def _do_build_basic_apps():
     all-clusters and chip-tool only, for basic tests.
     """
     logging.info("Building example apps...")
+    target_prefix = _get_native_machine_target()
     targets = [
-        "linux-x64-chip-tool-no-ble-clang-boringssl",
-        "linux-x64-all-clusters-no-ble-clang-boringssl",
+        f"{target_prefix}-chip-tool-no-ble-clang-boringssl",
+        f"{target_prefix}-all-clusters-no-ble-clang-boringssl",
     ]
 
     cmd = ["./scripts/build/build_examples.py"]
@@ -303,17 +321,18 @@ 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('out/linux-x64-all-clusters-no-ble-clang-boringssl/chip-all-clusters-app')}
-            CHIP_LOCK_APP: {as_runner('out/linux-x64-lock-no-ble-clang-boringssl/chip-lock-app')}
-            ENERGY_MANAGEMENT_APP: {as_runner('out/linux-x64-energy-management-no-ble-clang-boringssl/chip-energy-management-app')}
-            LIT_ICD_APP: {as_runner('out/linux-x64-lit-icd-no-ble-clang-boringssl/lit-icd-app')}
-            CHIP_MICROWAVE_OVEN_APP: {as_runner('out/linux-x64-microwave-oven-no-ble-clang-boringssl/chip-microwave-oven-app')}
-            CHIP_RVC_APP: {as_runner('out/linux-x64-rvc-no-ble-clang-boringssl/chip-rvc-app')}
-            NETWORK_MANAGEMENT_APP: {as_runner('out/linux-x64-network-manager-ipv6only-no-ble-clang-boringssl/matter-network-manager-app')}
+            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}}
@@ -512,10 +531,11 @@ def _do_build_fabric_sync_apps():
     """
     Build applications used for fabric sync tests
     """
+    target_prefix = _get_native_machine_target()
     targets = [
-        "linux-x64-fabric-bridge-boringssl-rpc-no-ble",
-        "linux-x64-fabric-admin-boringssl-rpc",
-        "linux-x64-all-clusters-boringssl-no-ble",
+        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"]
@@ -662,15 +682,17 @@ def chip_tool_tests(target, target_glob, include_tags, expected_failures, runner
         "chip_tool_python",
     ]
 
+    target_prefix = _get_native_machine_target()
     cmd.extend(
-        ["--chip-tool", "./out/linux-x64-chip-tool-no-ble-clang-boringssl/chip-tool"]
+        ["--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 include_tags ChangedPathLi
+        cmd.extend(["--include-ta
+                     *listener);gs", include_tags])
 
     if target_glob is not None:
         cmd.extend(["--target-glob", target_glob])
@@ -685,60 +707,63 @@ def chip_tool_tests(target, target_glob, include_tags, expected_failures, runner
     _add_target_to_cmd(
         cmd,
         "--all-clusters-app",
-        "./out/linux-x64-all-clusters-no-ble-clang-boringssl/chip-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",
-        "./out/linux-x64-lock-no-ble-clang-boringssl/chip-lock-app",
+        f"./out/{target-prefix}-lock-no-ble-clang-boringssl/chip-lock-app",
         runner,
     )
     _add_target_to_cmd(
         cmd,
         "--ota-provider-app",
-        "./out/linux-x64-ota-provider-no-ble-clang-boringssl/chip-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",
-        "./out/linux-x64-ota-requestor-no-ble-clang-boringssl/chip-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",
-        "./out/linux-x64-tv-app-no-ble-clang-boringssl/chip-tv-app",
+        f"./out/{target-prefix}-tv-app-no-ble-clang-boringssl/chip-tv-app",
         runner,
     )
     _add_target_to_cmd(
         cmd,
         "--bridge-app",
-        "./out/linux-x64-bridge-no-ble-clang-boringssl/chip-bridge-app",
+        f"./out/{target-prefix}-bridge-no-ble-clang-boringssl/chip-bridge-app",
         runner,
     )
     _add_target_to_cmd(
         cmd,
         "--lit-icd-app",
-        "./out/linux-x64-lit-icd-no-ble-clang-boringssl/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",
-        "./out/linux-x64-microwave-oven-no-ble-clang-boringssl/chip-microwave-oven-app",
-        runner,
+        f"./out/{target-prefix}-microwave-oven-no-ble-clang-boringssl/chip-microwave-oven-app",
+        runner, ChangedPa,
     )
+     listener
     _add_target_to_cmd(
         cmd,
         "--rvc-app",
-        "./out/linux-x64-rvc-no-ble-clang-boringssl/chip-rvc-app",
+        f"./out/{target-prefix}-rvc-no-ble-clang-boringssl/chip-rvc-app",
         runner,
-    )
+   , emberAfGloba )
+
 
     subprocess.run(_with_activate(cmd), check=True)
 
 
-if __name__ == "__main__":
+if _
+_name__ == "__main__":
     cli()

From ed0bd1cd0ac2df17156568df7514de348035fd2c Mon Sep 17 00:00:00 2001
From: Andrei Litvin <andreilitvin@google.com>
Date: Tue, 17 Sep 2024 11:03:09 -0400
Subject: [PATCH 06/13] Use local build platforms, to maybe be able to run
 under darwin

---
 scripts/tests/local.py | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/scripts/tests/local.py b/scripts/tests/local.py
index 93c0b30b86f128..1ecddb017eead5 100755
--- a/scripts/tests/local.py
+++ b/scripts/tests/local.py
@@ -684,7 +684,7 @@ def chip_tool_tests(target, target_glob, include_tags, expected_failures, runner
 
     target_prefix = _get_native_machine_target()
     cmd.extend(
-        ["--chip-tool", f"./out/{target-prefix}-chip-tool-no-ble-clang-boringssl/chip-tool"]
+        ["--chip-tool", f"./out/{target_prefix}-chip-tool-no-ble-clang-boringssl/chip-tool"]
     )
 
     if target is not None:
@@ -707,56 +707,56 @@ def chip_tool_tests(target, target_glob, include_tags, expected_failures, runner
     _add_target_to_cmd(
         cmd,
         "--all-clusters-app",
-        f"./out/{target-prefix}-all-clusters-no-ble-clang-boringssl/chip-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",
+        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",
+        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",
+        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",
+        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",
+        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",
+        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",
+        f"./out/{target_prefix}-microwave-oven-no-ble-clang-boringssl/chip-microwave-oven-app",
         runner, ChangedPa,
     )
      listener
     _add_target_to_cmd(
         cmd,
         "--rvc-app",
-        f"./out/{target-prefix}-rvc-no-ble-clang-boringssl/chip-rvc-app",
+        f"./out/{target_prefix}-rvc-no-ble-clang-boringssl/chip-rvc-app",
         runner,
    , emberAfGloba )
 

From 7ae1203e1beba3724877fe2a27f7b6cd892bc18b Mon Sep 17 00:00:00 2001
From: Andrei Litvin <andreilitvin@google.com>
Date: Tue, 17 Sep 2024 11:04:09 -0400
Subject: [PATCH 07/13] Fix typos

---
 scripts/tests/local.py | 20 ++++++++++++--------
 1 file changed, 12 insertions(+), 8 deletions(-)

diff --git a/scripts/tests/local.py b/scripts/tests/local.py
index 1ecddb017eead5..9c894b9e22ac30 100755
--- a/scripts/tests/local.py
+++ b/scripts/tests/local.py
@@ -20,6 +20,8 @@
 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'.
@@ -35,6 +37,7 @@ def _get_native_machine_target():
 
     return f"{current_system_info.system.lower()}-{arch}"
 
+
 class BinaryRunner(enum.Enum):
     """
     Enumeration describing a wrapper runner for an application. Useful for debugging
@@ -328,11 +331,14 @@ def as_runner(path):
                 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')}
+            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_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')}
+            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}}
@@ -690,9 +696,8 @@ def chip_tool_tests(target, target_glob, include_tags, expected_failures, runner
     if target is not None:
         cmd.extend(["--target", target])
 
-    if include_tags ChangedPathLi
-        cmd.extend(["--include-ta
-                     *listener);gs", include_tags])
+    if include_tags:
+        cmd.extend(["--include-tags", include_tags])
 
     if target_glob is not None:
         cmd.extend(["--target-glob", target_glob])
@@ -764,6 +769,5 @@ def chip_tool_tests(target, target_glob, include_tags, expected_failures, runner
     subprocess.run(_with_activate(cmd), check=True)
 
 
-if _
-_name__ == "__main__":
+if __name__ == "__main__":
     cli()

From 6cd44c566f145791cdb3e7104858ca4a40359177 Mon Sep 17 00:00:00 2001
From: Andrei Litvin <andreilitvin@google.com>
Date: Tue, 17 Sep 2024 11:06:13 -0400
Subject: [PATCH 08/13] More typo fixes

---
 scripts/tests/local.py | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/scripts/tests/local.py b/scripts/tests/local.py
index 9c894b9e22ac30..9a45f73dd0b125 100755
--- a/scripts/tests/local.py
+++ b/scripts/tests/local.py
@@ -696,7 +696,7 @@ def chip_tool_tests(target, target_glob, include_tags, expected_failures, runner
     if target is not None:
         cmd.extend(["--target", target])
 
-    if include_tags:
+    if include_tags is not None:
         cmd.extend(["--include-tags", include_tags])
 
     if target_glob is not None:
@@ -755,16 +755,14 @@ def chip_tool_tests(target, target_glob, include_tags, expected_failures, runner
         cmd,
         "--microwave-oven-app",
         f"./out/{target_prefix}-microwave-oven-no-ble-clang-boringssl/chip-microwave-oven-app",
-        runner, ChangedPa,
+        runner,
     )
-     listener
     _add_target_to_cmd(
         cmd,
         "--rvc-app",
         f"./out/{target_prefix}-rvc-no-ble-clang-boringssl/chip-rvc-app",
         runner,
-   , emberAfGloba )
-
+    )
 
     subprocess.run(_with_activate(cmd), check=True)
 

From 575b676f7cad6d06f301d7d289c997b2c0688cb5 Mon Sep 17 00:00:00 2001
From: Andrei Litvin <andreilitvin@google.com>
Date: Tue, 17 Sep 2024 11:06:32 -0400
Subject: [PATCH 09/13] Restyle

---
 scripts/tests/local.py | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/scripts/tests/local.py b/scripts/tests/local.py
index 9a45f73dd0b125..67ed679d372b42 100755
--- a/scripts/tests/local.py
+++ b/scripts/tests/local.py
@@ -146,18 +146,18 @@ def _do_build_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",
+        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"]

From 319ed44d7dc756c699b7c672f97e92b58809171c Mon Sep 17 00:00:00 2001
From: Andrei Litvin <andreilitvin@google.com>
Date: Tue, 17 Sep 2024 11:08:22 -0400
Subject: [PATCH 10/13] Make platform name actually work

---
 scripts/tests/local.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/scripts/tests/local.py b/scripts/tests/local.py
index 67ed679d372b42..e49b0bb6771d42 100755
--- a/scripts/tests/local.py
+++ b/scripts/tests/local.py
@@ -5,6 +5,7 @@
 import glob
 import logging
 import os
+import platform
 import shlex
 import stat
 import subprocess
@@ -26,7 +27,7 @@ def _get_native_machine_target():
     """
     Returns the build prefix for applications, such as 'linux-x64'.
     """
-    current_system_info = uname()
+    current_system_info = platform.uname()
     arch = current_system_info.machine
     if arch == 'x86_64':
         arch = 'x64'

From 386fa607701c640da47ebe54b0c24d170c4b51c8 Mon Sep 17 00:00:00 2001
From: "Restyled.io" <commits@restyled.io>
Date: Tue, 17 Sep 2024 15:13:40 +0000
Subject: [PATCH 11/13] Restyled by prettier-markdown

---
 docs/testing/python.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/docs/testing/python.md b/docs/testing/python.md
index b9aada78db08b3..c631550b040082 100644
--- a/docs/testing/python.md
+++ b/docs/testing/python.md
@@ -639,7 +639,8 @@ Ex:
 
 ## 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.
+`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:
 

From 815c255b04a459071daea4c29b93e07f75db449a Mon Sep 17 00:00:00 2001
From: Andrei Litvin <andreilitvin@google.com>
Date: Tue, 17 Sep 2024 11:23:02 -0400
Subject: [PATCH 12/13] Fix linter

---
 scripts/tests/local.py | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/scripts/tests/local.py b/scripts/tests/local.py
index e49b0bb6771d42..8158919b4206e8 100755
--- a/scripts/tests/local.py
+++ b/scripts/tests/local.py
@@ -442,7 +442,6 @@ def as_runner(path):
         "TC_CC_2_2.py": "1.5 minutes",
         "TC_DEM_2_10.py": "40 seconds",
         "TC_DeviceBasicComposition.py": "25 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",
@@ -594,8 +593,8 @@ def build_casting_apps(data_model_interface, asan):
         casting_args.append(f'chip_use_data_model_interface="{data_model_interface}"')
 
     if asan:
-        tv_args.append(f"is_asan=true is_clang=true")
-        casting_args.append(f"is_asan=true is_clang=true")
+        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)

From 4a0f9225100ec9b294f9aab67574e757f56513ae Mon Sep 17 00:00:00 2001
From: Andrei Litvin <andreilitvin@google.com>
Date: Tue, 17 Sep 2024 12:48:28 -0400
Subject: [PATCH 13/13] Add license

---
 scripts/tests/local.py | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/scripts/tests/local.py b/scripts/tests/local.py
index 8158919b4206e8..f44bdc2e546f34 100755
--- a/scripts/tests/local.py
+++ b/scripts/tests/local.py
@@ -1,5 +1,19 @@
 #!/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
@@ -456,6 +470,7 @@ def as_runner(path):
         "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 = []