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 previous 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 we are using command line platform filter, no need to list every 108 # other platform as excluded, we know that already. 109 # Show only the discards that apply to the selected platforms on the 110 # command line 111 112 if options.verbose > 0: 113 for i in tplan.instances.values(): 114 if i.status in [TwisterStatus.SKIP,TwisterStatus.FILTER]: 115 if options.platform and not tplan.check_platform(i.platform, options.platform): 116 continue 117 # Filtered tests should be visable only when verbosity > 1 118 if options.verbose < 2 and i.status == TwisterStatus.FILTER: 119 continue 120 res = i.reason 121 if "Quarantine" in i.reason: 122 res = "Quarantined" 123 logger.info( 124 f"{i.platform.name:<25} {i.testsuite.name:<50}" 125 f" {Fore.YELLOW}{i.status.upper()}{Fore.RESET}: {res}" 126 ) 127 128 report = Reporting(tplan, env) 129 plan_file = os.path.join(options.outdir, "testplan.json") 130 if not os.path.exists(plan_file): 131 report.json_report(plan_file, env.version) 132 133 if options.save_tests: 134 report.json_report(options.save_tests, env.version) 135 return 0 136 137 if options.report_summary is not None: 138 if options.report_summary < 0: 139 logger.error("The report summary value cannot be less than 0") 140 return 1 141 report.synopsis() 142 return 0 143 144 # FIXME: This is a workaround for the fact that the hardware map can be usng 145 # the short name of the platform, while the testplan is using the full name. 146 # 147 # convert platform names coming from the hardware map to the full target 148 # name. 149 # this is needed to match the platform names in the testplan. 150 for d in hwm.duts: 151 if d.platform in tplan.platform_names: 152 d.platform = tplan.get_platform(d.platform).name 153 154 if options.device_testing and not options.build_only: 155 print("\nDevice testing on:") 156 hwm.dump(filtered=tplan.selected_platforms) 157 print("") 158 159 if options.dry_run: 160 duration = time.time() - start_time 161 logger.info(f"Completed in {duration} seconds") 162 return 0 163 164 if options.short_build_path: 165 tplan.create_build_dir_links() 166 167 runner = TwisterRunner(tplan.instances, tplan.testsuites, env) 168 runner.duts = hwm.duts 169 runner.run() 170 171 # figure out which report to use for size comparison 172 report_to_use = None 173 if options.compare_report: 174 report_to_use = options.compare_report 175 elif options.last_metrics: 176 report_to_use = previous_results_file 177 178 report.footprint_reports( 179 report_to_use, 180 options.show_footprint, 181 options.all_deltas, 182 options.footprint_threshold, 183 options.last_metrics, 184 ) 185 186 duration = time.time() - start_time 187 188 if options.verbose > 1: 189 runner.results.summary() 190 191 report.summary(runner.results, duration) 192 193 report.coverage_status = True 194 if options.coverage and not options.disable_coverage_aggregation: 195 if not options.build_only: 196 report.coverage_status, report.coverage = run_coverage(options, tplan) 197 else: 198 logger.info("Skipping coverage report generation due to --build-only.") 199 200 if options.device_testing and not options.build_only: 201 hwm.summary(tplan.selected_platforms) 202 203 report.save_reports( 204 options.report_name, 205 options.report_suffix, 206 options.report_dir, 207 options.no_update, 208 options.platform_reports, 209 ) 210 211 report.synopsis() 212 213 if options.package_artifacts: 214 artifacts = Artifacts(env) 215 artifacts.package() 216 217 if ( 218 runner.results.failed 219 or runner.results.error 220 or (tplan.warnings and options.warnings_as_errors) 221 or (options.coverage and not report.coverage_status) 222 ): 223 if env.options.quit_on_failure: 224 logger.info("twister aborted because of a failure/error") 225 else: 226 logger.info("Run completed") 227 return 1 228 229 logger.info("Run completed") 230 return 0 231 232 233def main(options: argparse.Namespace, default_options: argparse.Namespace): 234 try: 235 return_code = twister(options, default_options) 236 finally: 237 close_logging() 238 return return_code 239