1# vim: set syntax=python ts=4 : 2# 3# Copyright (c) 2022 Google 4# SPDX-License-Identifier: Apache-2.0 5 6import argparse 7import logging 8import os 9import shutil 10import sys 11import time 12 13import colorama 14from colorama import Fore 15from twisterlib.coverage import run_coverage 16from twisterlib.environment import TwisterEnv 17from twisterlib.hardwaremap import HardwareMap 18from twisterlib.log_helper import close_logging, setup_logging 19from twisterlib.package import Artifacts 20from twisterlib.reports import Reporting 21from twisterlib.runner import TwisterRunner 22from twisterlib.statuses import TwisterStatus 23from twisterlib.testplan import TestPlan 24 25 26def init_color(colorama_strip): 27 colorama.init(strip=colorama_strip) 28 29 30def twister(options: argparse.Namespace, default_options: argparse.Namespace): 31 start_time = time.time() 32 33 # Configure color output 34 color_strip = False if options.force_color else None 35 36 colorama.init(strip=color_strip) 37 init_color(colorama_strip=color_strip) 38 39 previous_results = None 40 # Cleanup 41 if ( 42 options.no_clean 43 or options.only_failed 44 or options.test_only 45 or options.report_summary is not None 46 ): 47 if os.path.exists(options.outdir): 48 print("Keeping artifacts untouched") 49 elif options.last_metrics: 50 ls = os.path.join(options.outdir, "twister.json") 51 if os.path.exists(ls): 52 with open(ls) as fp: 53 previous_results = fp.read() 54 else: 55 sys.exit(f"Can't compare metrics with non existing file {ls}") 56 elif os.path.exists(options.outdir): 57 if options.clobber_output: 58 print(f"Deleting output directory {options.outdir}") 59 shutil.rmtree(options.outdir) 60 else: 61 for i in range(1, 100): 62 new_out = options.outdir + f".{i}" 63 if not os.path.exists(new_out): 64 print(f"Renaming output directory to {new_out}") 65 shutil.move(options.outdir, new_out) 66 break 67 else: 68 sys.exit(f"Too many '{options.outdir}.*' directories. Run either with --no-clean, " 69 "or --clobber-output, or delete these directories manually.") 70 71 previous_results_file = None 72 os.makedirs(options.outdir, exist_ok=True) 73 if options.last_metrics and previous_results: 74 previous_results_file = os.path.join(options.outdir, "baseline.json") 75 with open(previous_results_file, "w") as fp: 76 fp.write(previous_results) 77 78 setup_logging(options.outdir, options.log_file, options.log_level, options.timestamps) 79 logger = logging.getLogger("twister") 80 81 env = TwisterEnv(options, default_options) 82 env.discover() 83 84 hwm = HardwareMap(env) 85 ret = hwm.discover() 86 if ret == 0: 87 return 0 88 89 env.hwm = hwm 90 91 tplan = TestPlan(env) 92 try: 93 tplan.discover() 94 except RuntimeError as e: 95 logger.error(f"{e}") 96 return 1 97 98 if tplan.report() == 0: 99 return 0 100 101 try: 102 tplan.load() 103 except RuntimeError as e: 104 logger.error(f"{e}") 105 return 1 106 107 if options.verbose > 1: 108 # if we are using command line platform filter, no need to list every 109 # other platform as excluded, we know that already. 110 # Show only the discards that apply to the selected platforms on the 111 # command line 112 113 for i in tplan.instances.values(): 114 if i.status == TwisterStatus.FILTER: 115 if options.platform and not tplan.check_platform(i.platform, options.platform): 116 continue 117 logger.debug( 118 f"{i.platform.name:<25} {i.testsuite.name:<50}" 119 f" {Fore.YELLOW}FILTERED{Fore.RESET}: {i.reason}" 120 ) 121 122 report = Reporting(tplan, env) 123 plan_file = os.path.join(options.outdir, "testplan.json") 124 if not os.path.exists(plan_file): 125 report.json_report(plan_file, env.version) 126 127 if options.save_tests: 128 report.json_report(options.save_tests, env.version) 129 return 0 130 131 if options.report_summary is not None: 132 if options.report_summary < 0: 133 logger.error("The report summary value cannot be less than 0") 134 return 1 135 report.synopsis() 136 return 0 137 138 if options.device_testing and not options.build_only: 139 print("\nDevice testing on:") 140 hwm.dump(filtered=tplan.selected_platforms) 141 print("") 142 143 if options.dry_run: 144 duration = time.time() - start_time 145 logger.info(f"Completed in {duration} seconds") 146 return 0 147 148 if options.short_build_path: 149 tplan.create_build_dir_links() 150 151 runner = TwisterRunner(tplan.instances, tplan.testsuites, env) 152 # FIXME: This is a workaround for the fact that the hardware map can be usng 153 # the short name of the platform, while the testplan is using the full name. 154 # 155 # convert platform names coming from the hardware map to the full target 156 # name. 157 # this is needed to match the platform names in the testplan. 158 for d in hwm.duts: 159 if d.platform in tplan.platform_names: 160 d.platform = tplan.get_platform(d.platform).name 161 runner.duts = hwm.duts 162 runner.run() 163 164 # figure out which report to use for size comparison 165 report_to_use = None 166 if options.compare_report: 167 report_to_use = options.compare_report 168 elif options.last_metrics: 169 report_to_use = previous_results_file 170 171 report.footprint_reports( 172 report_to_use, 173 options.show_footprint, 174 options.all_deltas, 175 options.footprint_threshold, 176 options.last_metrics, 177 ) 178 179 duration = time.time() - start_time 180 181 if options.verbose > 1: 182 runner.results.summary() 183 184 report.summary(runner.results, options.disable_unrecognized_section_test, duration) 185 186 report.coverage_status = True 187 if options.coverage and not options.disable_coverage_aggregation: 188 if not options.build_only: 189 report.coverage_status, report.coverage = run_coverage(options, tplan) 190 else: 191 logger.info("Skipping coverage report generation due to --build-only.") 192 193 if options.device_testing and not options.build_only: 194 hwm.summary(tplan.selected_platforms) 195 196 report.save_reports( 197 options.report_name, 198 options.report_suffix, 199 options.report_dir, 200 options.no_update, 201 options.platform_reports, 202 ) 203 204 report.synopsis() 205 206 if options.package_artifacts: 207 artifacts = Artifacts(env) 208 artifacts.package() 209 210 if ( 211 runner.results.failed 212 or runner.results.error 213 or (tplan.warnings and options.warnings_as_errors) 214 or (options.coverage and not report.coverage_status) 215 ): 216 if env.options.quit_on_failure: 217 logger.info("twister aborted because of a failure/error") 218 else: 219 logger.info("Run completed") 220 return 1 221 222 logger.info("Run completed") 223 return 0 224 225 226def main(options: argparse.Namespace, default_options: argparse.Namespace): 227 try: 228 return_code = twister(options, default_options) 229 finally: 230 close_logging() 231 return return_code 232