#!/usr/bin/env python3 # vim: set syntax=python ts=4 : # # Copyright (c) 2018-2025 Intel Corporation # Copyright 2022 NXP # Copyright (c) 2024 Arm Limited (or its affiliates). All rights reserved. # # SPDX-License-Identifier: Apache-2.0 import argparse import json import logging import os import re import shutil import subprocess import sys from collections.abc import Generator from datetime import datetime, timezone from importlib import metadata from pathlib import Path import zephyr_module from twisterlib.constants import SUPPORTED_SIMS from twisterlib.coverage import supported_coverage_formats from twisterlib.error import TwisterRuntimeError from twisterlib.log_helper import log_command logger = logging.getLogger('twister') logger.setLevel(logging.DEBUG) ZEPHYR_BASE = os.getenv("ZEPHYR_BASE") if not ZEPHYR_BASE: sys.exit("$ZEPHYR_BASE environment variable undefined") # Use this for internal comparisons; that's what canonicalization is # for. Don't use it when invoking other components of the build system # to avoid confusing and hard to trace inconsistencies in error messages # and logs, generated Makefiles, etc. compared to when users invoke these # components directly. # Note "normalization" is different from canonicalization, see os.path. canonical_zephyr_base = os.path.realpath(ZEPHYR_BASE) def _get_installed_packages() -> Generator[str, None, None]: """Return list of installed python packages.""" for dist in metadata.distributions(): yield dist.metadata['Name'] def python_version_guard(): min_ver = (3, 10) if sys.version_info < min_ver: min_ver_str = '.'.join([str(v) for v in min_ver]) cur_ver_line = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" print(f"Unsupported Python version {cur_ver_line}.") print(f"Currently, Twister requires at least Python {min_ver_str}.") print("Install a newer Python version and retry.") sys.exit(1) installed_packages: list[str] = list(_get_installed_packages()) PYTEST_PLUGIN_INSTALLED = 'pytest-twister-harness' in installed_packages def norm_path(astring): newstring = os.path.normpath(astring).replace(os.sep, '/') return newstring def add_parse_arguments(parser = None) -> argparse.ArgumentParser: if parser is None: parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter, allow_abbrev=False) parser.fromfile_prefix_chars = "+" case_select = parser.add_argument_group("Test case selection", """ Artificially long but functional example: $ ./scripts/twister -v \\ --testsuite-root tests/ztest/base \\ --testsuite-root tests/kernel \\ --test tests/ztest/base/testing.ztest.verbose_0 \\ --test tests/kernel/fifo/fifo_api/kernel.fifo "kernel.fifo.poll" is one of the test section names in __/fifo_api/testcase.yaml """) test_plan_report = parser.add_argument_group( title="Test plan reporting", description="Report the composed test plan details and exit (dry-run)." ) test_plan_report_xor = test_plan_report.add_mutually_exclusive_group() platform_group_option = parser.add_mutually_exclusive_group() run_group_option = parser.add_mutually_exclusive_group() device = parser.add_mutually_exclusive_group() test_or_build = parser.add_mutually_exclusive_group() test_xor_subtest = case_select.add_mutually_exclusive_group() test_xor_generator = case_select.add_mutually_exclusive_group() valgrind_asan_group = parser.add_mutually_exclusive_group() footprint_group = parser.add_argument_group( title="Memory footprint", description="Collect and report ROM/RAM size footprint for the test instance images built.") coverage_group = parser.add_argument_group( title="Code coverage", description="Build with code coverage support, collect code coverage statistics " "executing tests, compose code coverage report at the end.\n" "Effective for devices with 'HAS_COVERAGE_SUPPORT'.") test_plan_report_xor.add_argument( "-E", "--save-tests", metavar="FILENAME", action="store", help="Write a list of tests and platforms to be run to %(metavar)s file and stop execution." " The resulting file will have the same content as 'testplan.json'." ) case_select.add_argument( "-F", "--load-tests", metavar="FILENAME", action="store", help="Load a list of tests and platforms to be run" "from a JSON file ('testplan.json' schema)." ) case_select.add_argument( "-T", "--testsuite-root", action="append", default=[], type = norm_path, help="Base directory to recursively search for test cases. All " "testcase.yaml files under here will be processed. May be " "called multiple times. Defaults to the 'samples/' and " "'tests/' directories at the base of the Zephyr tree.") case_select.add_argument( "-f", "--only-failed", action="store_true", help="Run only those tests that failed the previous twister run " "invocation.") test_plan_report_xor.add_argument("--list-tests", action="store_true", help="""List of all sub-test functions recursively found in all --testsuite-root arguments. The output is flattened and reports detailed sub-test names without their directories. Note: sub-test names can share the same test scenario identifier prefix (section.subsection) even if they are from different test projects. """) test_plan_report_xor.add_argument("--test-tree", action="store_true", help="""Output the test plan in a tree form.""") platform_group_option.add_argument( "-G", "--integration", action="store_true", help="Run integration tests") platform_group_option.add_argument( "--emulation-only", action="store_true", help="Only build and run emulation platforms") run_group_option.add_argument( "--device-testing", action="store_true", help="Test on device directly. Specify the serial device to " "use with the --device-serial option.") run_group_option.add_argument("--generate-hardware-map", help="""Probe serial devices connected to this platform and create a hardware map file to be used with --device-testing """) run_group_option.add_argument( "--simulation", dest="sim_name", choices=SUPPORTED_SIMS, help="Selects which simulation to use. Must match one of the names defined in the board's " "manifest. If multiple simulator are specified in the selected board and this " "argument is not passed, then the first simulator is selected.") device.add_argument("--device-serial", help="""Serial device for accessing the board (e.g., /dev/ttyACM0) """) device.add_argument("--device-serial-pty", help="""Script for controlling pseudoterminal. Twister believes that it interacts with a terminal when it actually interacts with the script. E.g "twister --device-testing --device-serial-pty