1#!/usr/bin/env python3 2# vim: set syntax=python ts=4 : 3# 4# Copyright (c) 2018 Intel Corporation 5# Copyright 2022 NXP 6# SPDX-License-Identifier: Apache-2.0 7 8import os 9import pkg_resources 10import sys 11from pathlib import Path 12import json 13import logging 14import subprocess 15import shutil 16import re 17import argparse 18from datetime import datetime, timezone 19from twisterlib.coverage import supported_coverage_formats 20 21logger = logging.getLogger('twister') 22logger.setLevel(logging.DEBUG) 23 24from twisterlib.error import TwisterRuntimeError 25from twisterlib.log_helper import log_command 26 27ZEPHYR_BASE = os.getenv("ZEPHYR_BASE") 28if not ZEPHYR_BASE: 29 sys.exit("$ZEPHYR_BASE environment variable undefined") 30 31sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/")) 32 33import zephyr_module 34 35# Use this for internal comparisons; that's what canonicalization is 36# for. Don't use it when invoking other components of the build system 37# to avoid confusing and hard to trace inconsistencies in error messages 38# and logs, generated Makefiles, etc. compared to when users invoke these 39# components directly. 40# Note "normalization" is different from canonicalization, see os.path. 41canonical_zephyr_base = os.path.realpath(ZEPHYR_BASE) 42 43installed_packages = [pkg.project_name for pkg in pkg_resources.working_set] # pylint: disable=not-an-iterable 44PYTEST_PLUGIN_INSTALLED = 'pytest-twister-harness' in installed_packages 45 46 47def add_parse_arguments(parser = None): 48 if parser is None: 49 parser = argparse.ArgumentParser( 50 description=__doc__, 51 formatter_class=argparse.RawDescriptionHelpFormatter, 52 allow_abbrev=False) 53 parser.fromfile_prefix_chars = "+" 54 55 case_select = parser.add_argument_group("Test case selection", 56 """ 57Artificially long but functional example: 58 $ ./scripts/twister -v \\ 59 --testsuite-root tests/ztest/base \\ 60 --testsuite-root tests/kernel \\ 61 --test tests/ztest/base/testing.ztest.verbose_0 \\ 62 --test tests/kernel/fifo/fifo_api/kernel.fifo 63 64 "kernel.fifo.poll" is one of the test section names in 65 __/fifo_api/testcase.yaml 66 """) 67 68 compare_group_option = parser.add_mutually_exclusive_group() 69 70 platform_group_option = parser.add_mutually_exclusive_group() 71 72 run_group_option = parser.add_mutually_exclusive_group() 73 74 device = parser.add_mutually_exclusive_group(required="--device-testing" in sys.argv) 75 76 test_or_build = parser.add_mutually_exclusive_group() 77 78 test_xor_subtest = case_select.add_mutually_exclusive_group() 79 80 test_xor_generator = case_select.add_mutually_exclusive_group() 81 82 valgrind_asan_group = parser.add_mutually_exclusive_group() 83 84 case_select.add_argument( 85 "-E", 86 "--save-tests", 87 metavar="FILENAME", 88 action="store", 89 help="Write a list of tests and platforms to be run to file.") 90 91 case_select.add_argument( 92 "-F", 93 "--load-tests", 94 metavar="FILENAME", 95 action="store", 96 help="Load a list of tests and platforms to be run from file.") 97 98 case_select.add_argument( 99 "-T", "--testsuite-root", action="append", default=[], 100 help="Base directory to recursively search for test cases. All " 101 "testcase.yaml files under here will be processed. May be " 102 "called multiple times. Defaults to the 'samples/' and " 103 "'tests/' directories at the base of the Zephyr tree.") 104 105 case_select.add_argument( 106 "-f", 107 "--only-failed", 108 action="store_true", 109 help="Run only those tests that failed the previous twister run " 110 "invocation.") 111 112 case_select.add_argument("--list-tests", action="store_true", 113 help="""List of all sub-test functions recursively found in 114 all --testsuite-root arguments. Note different sub-tests can share 115 the same section name and come from different directories. 116 The output is flattened and reports --sub-test names only, 117 not their directories. For instance net.socket.getaddrinfo_ok 118 and net.socket.fd_set belong to different directories. 119 """) 120 121 case_select.add_argument("--test-tree", action="store_true", 122 help="""Output the test plan in a tree form""") 123 124 compare_group_option.add_argument("--compare-report", 125 help="Use this report file for size comparison") 126 127 compare_group_option.add_argument( 128 "-m", "--last-metrics", action="store_true", 129 help="Compare with the results of the previous twister " 130 "invocation") 131 132 platform_group_option.add_argument( 133 "-G", 134 "--integration", 135 action="store_true", 136 help="Run integration tests") 137 138 platform_group_option.add_argument( 139 "--emulation-only", action="store_true", 140 help="Only build and run emulation platforms") 141 142 run_group_option.add_argument( 143 "--device-testing", action="store_true", 144 help="Test on device directly. Specify the serial device to " 145 "use with the --device-serial option.") 146 147 run_group_option.add_argument("--generate-hardware-map", 148 help="""Probe serial devices connected to this platform 149 and create a hardware map file to be used with 150 --device-testing 151 """) 152 153 device.add_argument("--device-serial", 154 help="""Serial device for accessing the board 155 (e.g., /dev/ttyACM0) 156 """) 157 158 device.add_argument("--device-serial-pty", 159 help="""Script for controlling pseudoterminal. 160 Twister believes that it interacts with a terminal 161 when it actually interacts with the script. 162 163 E.g "twister --device-testing 164 --device-serial-pty <script> 165 """) 166 167 device.add_argument("--hardware-map", 168 help="""Load hardware map from a file. This will be used 169 for testing on hardware that is listed in the file. 170 """) 171 172 parser.add_argument("--device-flash-timeout", type=int, default=60, 173 help="""Set timeout for the device flash operation in seconds. 174 """) 175 176 parser.add_argument("--device-flash-with-test", action="store_true", 177 help="""Add a test case timeout to the flash operation timeout 178 when flash operation also executes test case on the platform. 179 """) 180 181 test_or_build.add_argument( 182 "-b", "--build-only", action="store_true", default="--prep-artifacts-for-testing" in sys.argv, 183 help="Only build the code, do not attempt to run the code on targets.") 184 185 test_or_build.add_argument( 186 "--prep-artifacts-for-testing", action="store_true", 187 help="Generate artifacts for testing, do not attempt to run the" 188 "code on targets.") 189 190 parser.add_argument( 191 "--package-artifacts", 192 help="Package artifacts needed for flashing in a file to be used with --test-only" 193 ) 194 195 test_or_build.add_argument( 196 "--test-only", action="store_true", 197 help="""Only run device tests with current artifacts, do not build 198 the code""") 199 200 parser.add_argument("--timeout-multiplier", type=float, default=1, 201 help="""Globally adjust tests timeouts by specified multiplier. The resulting test 202 timeout would be multiplication of test timeout value, board-level timeout multiplier 203 and global timeout multiplier (this parameter)""") 204 205 test_xor_subtest.add_argument( 206 "-s", "--test", "--scenario", action="append", 207 help="Run only the specified testsuite scenario. These are named by " 208 "<path/relative/to/Zephyr/base/section.name.in.testcase.yaml>") 209 210 test_xor_subtest.add_argument( 211 "--sub-test", action="append", 212 help="""Recursively find sub-test functions and run the entire 213 test section where they were found, including all sibling test 214 functions. Sub-tests are named by: 215 section.name.in.testcase.yaml.function_name_without_test_prefix 216 Example: In kernel.fifo.fifo_loop: 'kernel.fifo' is a section name 217 and 'fifo_loop' is a name of a function found in main.c without test prefix. 218 """) 219 220 parser.add_argument( 221 "--pytest-args", action="append", 222 help="""Pass additional arguments to the pytest subprocess. This parameter 223 will override the pytest_args from the harness_config in YAML file. 224 """) 225 226 valgrind_asan_group.add_argument( 227 "--enable-valgrind", action="store_true", 228 help="""Run binary through valgrind and check for several memory access 229 errors. Valgrind needs to be installed on the host. This option only 230 works with host binaries such as those generated for the native_sim 231 configuration and is mutual exclusive with --enable-asan. 232 """) 233 234 valgrind_asan_group.add_argument( 235 "--enable-asan", action="store_true", 236 help="""Enable address sanitizer to check for several memory access 237 errors. Libasan needs to be installed on the host. This option only 238 works with host binaries such as those generated for the native_sim 239 configuration and is mutual exclusive with --enable-valgrind. 240 """) 241 242 # Start of individual args place them in alpha-beta order 243 244 board_root_list = ["%s/boards" % ZEPHYR_BASE, 245 "%s/scripts/pylib/twister/boards" % ZEPHYR_BASE] 246 247 modules = zephyr_module.parse_modules(ZEPHYR_BASE) 248 for module in modules: 249 board_root = module.meta.get("build", {}).get("settings", {}).get("board_root") 250 if board_root: 251 board_root_list.append(os.path.join(module.project, board_root, "boards")) 252 253 parser.add_argument( 254 "-A", "--board-root", action="append", default=board_root_list, 255 help="""Directory to search for board configuration files. All .yaml 256files in the directory will be processed. The directory should have the same 257structure in the main Zephyr tree: boards/<arch>/<board_name>/""") 258 259 parser.add_argument( 260 "--allow-installed-plugin", action="store_true", default=None, 261 help="Allow to use pytest plugin installed by pip for pytest tests." 262 ) 263 264 parser.add_argument( 265 "-a", "--arch", action="append", 266 help="Arch filter for testing. Takes precedence over --platform. " 267 "If unspecified, test all arches. Multiple invocations " 268 "are treated as a logical 'or' relationship") 269 270 parser.add_argument( 271 "-B", "--subset", 272 help="Only run a subset of the tests, 1/4 for running the first 25%%, " 273 "3/5 means run the 3rd fifth of the total. " 274 "This option is useful when running a large number of tests on " 275 "different hosts to speed up execution time.") 276 277 parser.add_argument( 278 "--shuffle-tests", action="store_true", default=None, 279 help="""Shuffle test execution order to get randomly distributed tests across subsets. 280 Used only when --subset is provided.""") 281 282 parser.add_argument( 283 "--shuffle-tests-seed", action="store", default=None, 284 help="""Seed value for random generator used to shuffle tests. 285 If not provided, seed in generated by system. 286 Used only when --shuffle-tests is provided.""") 287 288 parser.add_argument("-C", "--coverage", action="store_true", 289 help="Generate coverage reports. Implies " 290 "--enable-coverage.") 291 292 parser.add_argument( 293 "-c", "--clobber-output", action="store_true", 294 help="Cleaning the output directory will simply delete it instead " 295 "of the default policy of renaming.") 296 297 parser.add_argument( 298 "--cmake-only", action="store_true", 299 help="Only run cmake, do not build or run.") 300 301 parser.add_argument("--coverage-basedir", default=ZEPHYR_BASE, 302 help="Base source directory for coverage report.") 303 304 parser.add_argument("--coverage-platform", action="append", default=[], 305 help="Platforms to run coverage reports on. " 306 "This option may be used multiple times. " 307 "Default to what was selected with --platform.") 308 309 parser.add_argument("--coverage-tool", choices=['lcov', 'gcovr'], default='gcovr', 310 help="Tool to use to generate coverage report.") 311 312 parser.add_argument("--coverage-formats", action="store", default=None, # default behavior is set in run_coverage 313 help="Output formats to use for generated coverage reports, as a comma-separated list. " + 314 "Valid options for 'gcovr' tool are: " + 315 ','.join(supported_coverage_formats['gcovr']) + " (html - default)." + 316 " Valid options for 'lcov' tool are: " + 317 ','.join(supported_coverage_formats['lcov']) + " (html,lcov - default).") 318 319 parser.add_argument("--test-config", action="store", default=os.path.join(ZEPHYR_BASE, "tests", "test_config.yaml"), 320 help="Path to file with plans and test configurations.") 321 322 parser.add_argument("--level", action="store", 323 help="Test level to be used. By default, no levels are used for filtering" 324 "and do the selection based on existing filters.") 325 326 parser.add_argument( 327 "-D", "--all-deltas", action="store_true", 328 help="Show all footprint deltas, positive or negative. Implies " 329 "--footprint-threshold=0") 330 331 parser.add_argument( 332 "--device-serial-baud", action="store", default=None, 333 help="Serial device baud rate (default 115200)") 334 335 parser.add_argument("--disable-asserts", action="store_false", 336 dest="enable_asserts", 337 help="deprecated, left for compatibility") 338 339 parser.add_argument( 340 "--disable-unrecognized-section-test", action="store_true", 341 default=False, 342 help="Skip the 'unrecognized section' test.") 343 344 parser.add_argument( 345 "--disable-suite-name-check", action="store_true", default=False, 346 help="Disable extended test suite name verification at the beginning " 347 "of Ztest test. This option could be useful for tests or " 348 "platforms, which from some reasons cannot print early logs.") 349 350 parser.add_argument("-e", "--exclude-tag", action="append", 351 help="Specify tags of tests that should not run. " 352 "Default is to run all tests with all tags.") 353 354 parser.add_argument("--enable-coverage", action="store_true", 355 help="Enable code coverage using gcov.") 356 357 parser.add_argument( 358 "--enable-lsan", action="store_true", 359 help="""Enable leak sanitizer to check for heap memory leaks. 360 Libasan needs to be installed on the host. This option only 361 works with host binaries such as those generated for the native_sim 362 configuration and when --enable-asan is given. 363 """) 364 365 parser.add_argument( 366 "--enable-ubsan", action="store_true", 367 help="""Enable undefined behavior sanitizer to check for undefined 368 behaviour during program execution. It uses an optional runtime library 369 to provide better error diagnostics. This option only works with host 370 binaries such as those generated for the native_sim configuration. 371 """) 372 373 parser.add_argument("--enable-size-report", action="store_true", 374 help="Enable expensive computation of RAM/ROM segment sizes.") 375 376 parser.add_argument("--create-rom-ram-report", action="store_true", 377 help="Generate detailed ram/rom json reports for " 378 "each build, via cmake build calls with the " 379 "`--target footprint` argument") 380 381 parser.add_argument( 382 "--filter", choices=['buildable', 'runnable'], 383 default='buildable', 384 help="""Filter tests to be built and executed. By default everything is 385 built and if a test is runnable (emulation or a connected device), it 386 is run. This option allows for example to only build tests that can 387 actually be run. Runnable is a subset of buildable.""") 388 389 parser.add_argument("--force-color", action="store_true", 390 help="Always output ANSI color escape sequences " 391 "even when the output is redirected (not a tty)") 392 393 parser.add_argument("--force-toolchain", action="store_true", 394 help="Do not filter based on toolchain, use the set " 395 " toolchain unconditionally") 396 397 parser.add_argument("--gcov-tool", type=Path, default=None, 398 help="Path to the gcov tool to use for code coverage " 399 "reports") 400 401 parser.add_argument( 402 "-H", "--footprint-threshold", type=float, default=5, 403 help="When checking test case footprint sizes, warn the user if " 404 "the new app size is greater then the specified percentage " 405 "from the last release. Default is 5. 0 to warn on any " 406 "increase on app size.") 407 408 parser.add_argument( 409 "-i", "--inline-logs", action="store_true", 410 help="Upon test failure, print relevant log data to stdout " 411 "instead of just a path to it.") 412 413 parser.add_argument("--ignore-platform-key", action="store_true", 414 help="Do not filter based on platform key") 415 416 parser.add_argument( 417 "-j", "--jobs", type=int, 418 help="Number of jobs for building, defaults to number of CPU threads, " 419 "overcommitted by factor 2 when --build-only.") 420 421 parser.add_argument( 422 "-K", "--force-platform", action="store_true", 423 help="""Force testing on selected platforms, 424 even if they are excluded in the test configuration (testcase.yaml).""" 425 ) 426 427 parser.add_argument( 428 "-l", "--all", action="store_true", 429 help="Build/test on all platforms. Any --platform arguments " 430 "ignored.") 431 432 parser.add_argument("--list-tags", action="store_true", 433 help="List all tags occurring in selected tests.") 434 435 parser.add_argument("--log-file", metavar="FILENAME", action="store", 436 help="Specify a file where to save logs.") 437 438 parser.add_argument( 439 "-M", "--runtime-artifact-cleanup", choices=['pass', 'all'], 440 default=None, const='pass', nargs='?', 441 help="""Cleanup test artifacts. The default behavior is 'pass' 442 which only removes artifacts of passing tests. If you wish to 443 remove all artificats including those of failed tests, use 'all'.""") 444 445 test_xor_generator.add_argument( 446 "-N", "--ninja", action="store_true", 447 default=not any(a in sys.argv for a in ("-k", "--make")), 448 help="Use the Ninja generator with CMake. (This is the default)") 449 450 test_xor_generator.add_argument( 451 "-k", "--make", action="store_true", 452 help="Use the unix Makefile generator with CMake.") 453 454 parser.add_argument( 455 "-n", "--no-clean", action="store_true", 456 help="Re-use the outdir before building. Will result in " 457 "faster compilation since builds will be incremental.") 458 459 parser.add_argument( 460 "--aggressive-no-clean", action="store_true", 461 help="Re-use the outdir before building and do not re-run cmake. Will result in " 462 "much faster compilation since builds will be incremental. This option might " 463 " result in build failures and inconsistencies if dependencies change or when " 464 " applied on a significantly changed code base. Use on your own " 465 " risk. It is recommended to only use this option for local " 466 " development and when testing localized change in a subsystem.") 467 468 parser.add_argument( 469 '--detailed-test-id', action='store_true', 470 help="Include paths to tests' locations in tests' names. Names will follow " 471 "PATH_TO_TEST/SCENARIO_NAME schema " 472 "e.g. samples/hello_world/sample.basic.helloworld") 473 474 parser.add_argument( 475 "--no-detailed-test-id", dest='detailed_test_id', action="store_false", 476 help="Don't put paths into tests' names. " 477 "With this arg a test name will be a scenario name " 478 "e.g. sample.basic.helloworld.") 479 480 # Include paths in names by default. 481 parser.set_defaults(detailed_test_id=True) 482 483 # To be removed in favor of --detailed-skipped-report 484 parser.add_argument( 485 "--no-skipped-report", action="store_true", 486 help="""Do not report skipped test cases in junit output. [Experimental] 487 """) 488 489 parser.add_argument( 490 "--detailed-skipped-report", action="store_true", 491 help="Generate a detailed report with all skipped test cases" 492 "including those that are filtered based on testsuite definition." 493 ) 494 495 parser.add_argument( 496 "-O", "--outdir", 497 default=os.path.join(os.getcwd(), "twister-out"), 498 help="Output directory for logs and binaries. " 499 "Default is 'twister-out' in the current directory. " 500 "This directory will be cleaned unless '--no-clean' is set. " 501 "The '--clobber-output' option controls what cleaning does.") 502 503 parser.add_argument( 504 "-o", "--report-dir", 505 help="""Output reports containing results of the test run into the 506 specified directory. 507 The output will be both in JSON and JUNIT format 508 (twister.json and twister.xml). 509 """) 510 511 parser.add_argument("--overflow-as-errors", action="store_true", 512 help="Treat RAM/SRAM overflows as errors.") 513 514 515 parser.add_argument("-P", "--exclude-platform", action="append", default=[], 516 help="""Exclude platforms and do not build or run any tests 517 on those platforms. This option can be called multiple times. 518 """ 519 ) 520 521 parser.add_argument("--persistent-hardware-map", action='store_true', 522 help="""With --generate-hardware-map, tries to use 523 persistent names for serial devices on platforms 524 that support this feature (currently only Linux). 525 """) 526 527 parser.add_argument( 528 "--vendor", action="append", default=[], 529 help="Vendor filter for testing") 530 531 parser.add_argument( 532 "-p", "--platform", action="append", default=[], 533 help="Platform filter for testing. This option may be used multiple " 534 "times. Test suites will only be built/run on the platforms " 535 "specified. If this option is not used, then platforms marked " 536 "as default in the platform metadata file will be chosen " 537 "to build and test. ") 538 539 parser.add_argument( 540 "--platform-reports", action="store_true", 541 help="""Create individual reports for each platform. 542 """) 543 544 parser.add_argument("--pre-script", 545 help="""specify a pre script. This will be executed 546 before device handler open serial port and invoke runner. 547 """) 548 549 parser.add_argument("-Q", "--error-on-deprecations", action="store_false", 550 help="Error on deprecation warnings.") 551 552 parser.add_argument( 553 "--quarantine-list", 554 action="append", 555 metavar="FILENAME", 556 help="Load list of test scenarios under quarantine. The entries in " 557 "the file need to correspond to the test scenarios names as in " 558 "corresponding tests .yaml files. These scenarios " 559 "will be skipped with quarantine as the reason.") 560 561 parser.add_argument( 562 "--quarantine-verify", 563 action="store_true", 564 help="Use the list of test scenarios under quarantine and run them" 565 "to verify their current status.") 566 567 parser.add_argument("-R", "--enable-asserts", action="store_true", 568 default=True, 569 help="deprecated, left for compatibility") 570 571 parser.add_argument( 572 "--report-name", 573 help="""Create a report with a custom name. 574 """) 575 576 parser.add_argument( 577 "--report-suffix", 578 help="""Add a suffix to all generated file names, for example to add a 579 version or a commit ID. 580 """) 581 582 parser.add_argument( 583 "--retry-failed", type=int, default=0, 584 help="Retry failing tests again, up to the number of times specified.") 585 586 parser.add_argument( 587 "--retry-interval", type=int, default=60, 588 help="Retry failing tests after specified period of time.") 589 590 parser.add_argument( 591 "--retry-build-errors", action="store_true", 592 help="Retry build errors as well.") 593 594 parser.add_argument( 595 "-S", "--enable-slow", action="store_true", 596 default="--enable-slow-only" in sys.argv, 597 help="Execute time-consuming test cases that have been marked " 598 "as 'slow' in testcase.yaml. Normally these are only built.") 599 600 parser.add_argument( 601 "--enable-slow-only", action="store_true", 602 help="Execute time-consuming test cases that have been marked " 603 "as 'slow' in testcase.yaml only. This also set the option --enable-slow") 604 605 parser.add_argument( 606 "--seed", type=int, 607 help="Seed for native_sim pseudo-random number generator") 608 609 parser.add_argument( 610 "--short-build-path", 611 action="store_true", 612 help="Create shorter build directory paths based on symbolic links. " 613 "The shortened build path will be used by CMake for generating " 614 "the build system and executing the build. Use this option if " 615 "you experience build failures related to path length, for " 616 "example on Windows OS. This option can be used only with " 617 "'--ninja' argument (to use Ninja build generator).") 618 619 parser.add_argument( 620 "--show-footprint", 621 action="store_true", 622 required = "--footprint-from-buildlog" in sys.argv, 623 help="Show footprint statistics and deltas since last release." 624 ) 625 626 parser.add_argument( 627 "-t", "--tag", action="append", 628 help="Specify tags to restrict which tests to run by tag value. " 629 "Default is to not do any tag filtering. Multiple invocations " 630 "are treated as a logical 'or' relationship.") 631 632 parser.add_argument("--timestamps", 633 action="store_true", 634 help="Print all messages with time stamps.") 635 636 parser.add_argument( 637 "-u", 638 "--no-update", 639 action="store_true", 640 help="Do not update the results of the last run of twister.") 641 642 parser.add_argument( 643 "-v", 644 "--verbose", 645 action="count", 646 default=0, 647 help="Emit debugging information, call multiple times to increase " 648 "verbosity.") 649 650 parser.add_argument("-W", "--disable-warnings-as-errors", action="store_true", 651 help="Do not treat warning conditions as errors.") 652 653 parser.add_argument( 654 "--west-flash", nargs='?', const=[], 655 help="""Uses west instead of ninja or make to flash when running with 656 --device-testing. Supports comma-separated argument list. 657 658 E.g "twister --device-testing --device-serial /dev/ttyACM0 659 --west-flash="--board-id=foobar,--erase" 660 will translate to "west flash -- --board-id=foobar --erase" 661 662 NOTE: device-testing must be enabled to use this option. 663 """ 664 ) 665 parser.add_argument( 666 "--west-runner", 667 help="""Uses the specified west runner instead of default when running 668 with --west-flash. 669 670 E.g "twister --device-testing --device-serial /dev/ttyACM0 671 --west-flash --west-runner=pyocd" 672 will translate to "west flash --runner pyocd" 673 674 NOTE: west-flash must be enabled to use this option. 675 """ 676 ) 677 678 parser.add_argument( 679 "-X", "--fixture", action="append", default=[], 680 help="Specify a fixture that a board might support.") 681 682 parser.add_argument( 683 "-x", "--extra-args", action="append", default=[], 684 help="""Extra CMake cache entries to define when building test cases. 685 May be called multiple times. The key-value entries will be 686 prefixed with -D before being passed to CMake. 687 E.g 688 "twister -x=USE_CCACHE=0" 689 will translate to 690 "cmake -DUSE_CCACHE=0" 691 which will ultimately disable ccache. 692 """ 693 ) 694 695 parser.add_argument( 696 "-y", "--dry-run", action="store_true", 697 help="""Create the filtered list of test cases, but don't actually 698 run them. Useful if you're just interested in the test plan 699 generated for every run and saved in the specified output 700 directory (testplan.json). 701 """) 702 703 parser.add_argument( 704 "-z", "--size", action="append", 705 help="Don't run twister. Instead, produce a report to " 706 "stdout detailing RAM/ROM sizes on the specified filenames. " 707 "All other command line arguments ignored.") 708 709 parser.add_argument( 710 "--footprint-from-buildlog", 711 action = "store_true", 712 help="Get information about memory footprint from generated build.log. " 713 "Requires using --show-footprint option.") 714 715 parser.add_argument("extra_test_args", nargs=argparse.REMAINDER, 716 help="Additional args following a '--' are passed to the test binary") 717 718 parser.add_argument("--alt-config-root", action="append", default=[], 719 help="Alternative test configuration root/s. When a test is found, " 720 "Twister will check if a test configuration file exist in any of " 721 "the alternative test configuration root folders. For example, " 722 "given $test_root/tests/foo/testcase.yaml, Twister will use " 723 "$alt_config_root/tests/foo/testcase.yaml if it exists") 724 725 return parser 726 727 728def parse_arguments(parser, args, options = None): 729 if options is None: 730 options = parser.parse_args(args) 731 732 # Very early error handling 733 if options.short_build_path and not options.ninja: 734 logger.error("--short-build-path requires Ninja to be enabled") 735 sys.exit(1) 736 737 if options.device_serial_pty and os.name == "nt": # OS is Windows 738 logger.error("--device-serial-pty is not supported on Windows OS") 739 sys.exit(1) 740 741 if options.west_runner and options.west_flash is None: 742 logger.error("west-runner requires west-flash to be enabled") 743 sys.exit(1) 744 745 if options.west_flash and not options.device_testing: 746 logger.error("west-flash requires device-testing to be enabled") 747 sys.exit(1) 748 749 if not options.testsuite_root: 750 # if we specify a test scenario which is part of a suite directly, do 751 # not set testsuite root to default, just point to the test directory 752 # directly. 753 if options.test: 754 for scenario in options.test: 755 if dirname := os.path.dirname(scenario): 756 options.testsuite_root.append(dirname) 757 758 # check again and make sure we have something set 759 if not options.testsuite_root: 760 options.testsuite_root = [os.path.join(ZEPHYR_BASE, "tests"), 761 os.path.join(ZEPHYR_BASE, "samples")] 762 763 if options.show_footprint or options.compare_report: 764 options.enable_size_report = True 765 766 if options.aggressive_no_clean: 767 options.no_clean = True 768 769 if options.coverage: 770 options.enable_coverage = True 771 772 if not options.coverage_platform: 773 options.coverage_platform = options.platform 774 775 if options.coverage_formats: 776 for coverage_format in options.coverage_formats.split(','): 777 if coverage_format not in supported_coverage_formats[options.coverage_tool]: 778 logger.error(f"Unsupported coverage report formats:'{options.coverage_formats}' " 779 f"for {options.coverage_tool}") 780 sys.exit(1) 781 782 if options.enable_valgrind and not shutil.which("valgrind"): 783 logger.error("valgrind enabled but valgrind executable not found") 784 sys.exit(1) 785 786 if options.device_testing and (options.device_serial or options.device_serial_pty) and len(options.platform) > 1: 787 logger.error("""When --device-testing is used with 788 --device-serial or --device-serial-pty, 789 only one platform is allowed""") 790 sys.exit(1) 791 792 if options.device_flash_with_test and not options.device_testing: 793 logger.error("--device-flash-with-test requires --device_testing") 794 sys.exit(1) 795 796 if options.shuffle_tests and options.subset is None: 797 logger.error("--shuffle-tests requires --subset") 798 sys.exit(1) 799 800 if options.shuffle_tests_seed and options.shuffle_tests is None: 801 logger.error("--shuffle-tests-seed requires --shuffle-tests") 802 sys.exit(1) 803 804 if options.size: 805 from twisterlib.size_calc import SizeCalculator 806 for fn in options.size: 807 sc = SizeCalculator(fn, []) 808 sc.size_report() 809 sys.exit(0) 810 811 if len(options.extra_test_args) > 0: 812 # extra_test_args is a list of CLI args that Twister did not recognize 813 # and are intended to be passed through to the ztest executable. This 814 # list should begin with a "--". If not, there is some extra 815 # unrecognized arg(s) that shouldn't be there. Tell the user there is a 816 # syntax error. 817 if options.extra_test_args[0] != "--": 818 try: 819 double_dash = options.extra_test_args.index("--") 820 except ValueError: 821 double_dash = len(options.extra_test_args) 822 unrecognized = " ".join(options.extra_test_args[0:double_dash]) 823 824 logger.error("Unrecognized arguments found: '%s'. Use -- to " 825 "delineate extra arguments for test binary or pass " 826 "-h for help.", 827 unrecognized) 828 829 sys.exit(1) 830 831 # Strip off the initial "--" following validation. 832 options.extra_test_args = options.extra_test_args[1:] 833 834 if not options.allow_installed_plugin and PYTEST_PLUGIN_INSTALLED: 835 logger.error("By default Twister should work without pytest-twister-harness " 836 "plugin being installed, so please, uninstall it by " 837 "`pip uninstall pytest-twister-harness` and `git clean " 838 "-dxf scripts/pylib/pytest-twister-harness`.") 839 sys.exit(1) 840 elif options.allow_installed_plugin and PYTEST_PLUGIN_INSTALLED: 841 logger.warning("You work with installed version of " 842 "pytest-twister-harness plugin.") 843 844 return options 845 846 847class TwisterEnv: 848 849 def __init__(self, options=None) -> None: 850 self.version = "Unknown" 851 self.toolchain = None 852 self.commit_date = "Unknown" 853 self.run_date = None 854 self.options = options 855 856 if options and options.ninja: 857 self.generator_cmd = "ninja" 858 self.generator = "Ninja" 859 else: 860 self.generator_cmd = "make" 861 self.generator = "Unix Makefiles" 862 logger.info(f"Using {self.generator}..") 863 864 self.test_roots = options.testsuite_root if options else None 865 866 if options: 867 if not isinstance(options.board_root, list): 868 self.board_roots = [self.options.board_root] 869 else: 870 self.board_roots = self.options.board_root 871 self.outdir = os.path.abspath(options.outdir) 872 else: 873 self.board_roots = None 874 self.outdir = None 875 876 self.snippet_roots = [Path(ZEPHYR_BASE)] 877 modules = zephyr_module.parse_modules(ZEPHYR_BASE) 878 for module in modules: 879 snippet_root = module.meta.get("build", {}).get("settings", {}).get("snippet_root") 880 if snippet_root: 881 self.snippet_roots.append(Path(module.project) / snippet_root) 882 883 self.hwm = None 884 885 self.test_config = options.test_config if options else None 886 887 self.alt_config_root = options.alt_config_root if options else None 888 889 def discover(self): 890 self.check_zephyr_version() 891 self.get_toolchain() 892 self.run_date = datetime.now(timezone.utc).isoformat(timespec='seconds') 893 894 def check_zephyr_version(self): 895 try: 896 subproc = subprocess.run(["git", "describe", "--abbrev=12", "--always"], 897 stdout=subprocess.PIPE, 898 universal_newlines=True, 899 cwd=ZEPHYR_BASE) 900 if subproc.returncode == 0: 901 _version = subproc.stdout.strip() 902 if _version: 903 self.version = _version 904 logger.info(f"Zephyr version: {self.version}") 905 except OSError: 906 logger.exception("Failure while reading Zephyr version.") 907 908 if self.version == "Unknown": 909 logger.warning("Could not determine version") 910 911 try: 912 subproc = subprocess.run(["git", "show", "-s", "--format=%cI", "HEAD"], 913 stdout=subprocess.PIPE, 914 universal_newlines=True, 915 cwd=ZEPHYR_BASE) 916 if subproc.returncode == 0: 917 self.commit_date = subproc.stdout.strip() 918 except OSError: 919 logger.exception("Failure while reading head commit date.") 920 921 @staticmethod 922 def run_cmake_script(args=[]): 923 script = os.fspath(args[0]) 924 925 logger.debug("Running cmake script %s", script) 926 927 cmake_args = ["-D{}".format(a.replace('"', '')) for a in args[1:]] 928 cmake_args.extend(['-P', script]) 929 930 cmake = shutil.which('cmake') 931 if not cmake: 932 msg = "Unable to find `cmake` in path" 933 logger.error(msg) 934 raise Exception(msg) 935 cmd = [cmake] + cmake_args 936 log_command(logger, "Calling cmake", cmd) 937 938 kwargs = dict() 939 kwargs['stdout'] = subprocess.PIPE 940 # CMake sends the output of message() to stderr unless it's STATUS 941 kwargs['stderr'] = subprocess.STDOUT 942 943 p = subprocess.Popen(cmd, **kwargs) 944 out, _ = p.communicate() 945 946 # It might happen that the environment adds ANSI escape codes like \x1b[0m, 947 # for instance if twister is executed from inside a makefile. In such a 948 # scenario it is then necessary to remove them, as otherwise the JSON decoding 949 # will fail. 950 ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') 951 out = ansi_escape.sub('', out.decode()) 952 953 if p.returncode == 0: 954 msg = "Finished running %s" % (args[0]) 955 logger.debug(msg) 956 results = {"returncode": p.returncode, "msg": msg, "stdout": out} 957 958 else: 959 logger.error("Cmake script failure: %s" % (args[0])) 960 results = {"returncode": p.returncode, "returnmsg": out} 961 962 return results 963 964 def get_toolchain(self): 965 toolchain_script = Path(ZEPHYR_BASE) / Path('cmake/verify-toolchain.cmake') 966 result = self.run_cmake_script([toolchain_script, "FORMAT=json"]) 967 968 try: 969 if result['returncode']: 970 raise TwisterRuntimeError(f"E: {result['returnmsg']}") 971 except Exception as e: 972 print(str(e)) 973 sys.exit(2) 974 self.toolchain = json.loads(result['stdout'])['ZEPHYR_TOOLCHAIN_VARIANT'] 975 logger.info(f"Using '{self.toolchain}' toolchain.") 976