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