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