#!/usr/bin/env python3 # vim: set syntax=python ts=4 : # # Copyright (c) 2018 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 datetime import datetime, timezone from importlib import metadata from pathlib import Path from typing import Generator, List from twisterlib.coverage import supported_coverage_formats logger = logging.getLogger('twister') logger.setLevel(logging.DEBUG) from twisterlib.error import TwisterRuntimeError from twisterlib.log_helper import log_command ZEPHYR_BASE = os.getenv("ZEPHYR_BASE") if not ZEPHYR_BASE: sys.exit("$ZEPHYR_BASE environment variable undefined") sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/")) import zephyr_module # 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): 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.") 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. Note different sub-tests can share the same section name and come from different directories. The output is flattened and reports --sub-test names only, not their directories. For instance net.socket.getaddrinfo_ok and net.socket.fd_set belong to different directories. """) 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 """) 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