1#!/usr/bin/env python3 2# coding=utf-8 3 4# 5# Copyright (c) 2022 Huawei Device Co., Ltd. 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17# 18 19import os 20import threading 21import time 22 23from devicetest.core.constants import RunResult 24from devicetest.core.constants import RunSection 25from devicetest.core.error_message import ErrorMessage 26from devicetest.core.exception import DeviceTestError 27from devicetest.core.record import ProjectRecord 28from devicetest.log.logger import DeviceTestLog as log 29from devicetest.utils.util import get_base_name 30from xdevice import is_env_pool_run_mode 31from xdevice import Variables 32 33 34def get_decrypt_resource_path(): 35 return Variables.res_dir 36 37 38def set_resource_path(resource_path): 39 DeccVariable.project.resource_path = resource_path 40 41 42def get_testsuit_path(): 43 return DeccVariable.project.test_suite_path 44 45 46def get_project_path(): 47 """ 48 get project path 49 :return: prcject_path 50 """ 51 try: 52 if DeccVariable.project.project_path: 53 return DeccVariable.project.project_path 54 55 project_path = os.path.dirname(Variables.top_dir) 56 if project_path is None: 57 log.info("project path is None.") 58 raise Exception("") 59 if not os.path.exists(project_path): 60 log.info("project path not exists.") 61 log.debug("project path:{}".format(project_path)) 62 raise Exception("") 63 return project_path 64 65 except Exception as error: 66 log.error(ErrorMessage.Error_01428.Message.en, 67 error_no=ErrorMessage.Error_01428.Code, 68 is_traceback=True) 69 70 raise DeviceTestError(ErrorMessage.Error_01428.Topic) from error 71 72 73class CurCase: 74 75 def __init__(self, _log): 76 # 用例级别参数 77 self.log = _log 78 self.step_total = 0 # tests 数 79 self.run_section = "" # RunSection.SETUP 80 self.case_result = RunResult.PASSED # 当前用例执行结果 81 self.name = '' # 类方法名,即:用例名case_id 82 self.suite_name = "" # 用例对应哪个测试套 83 self.error_msg = '' # 用例失败信息 84 self.case_screenshot_dir = None # 当前用例失败截图的图片保存路径 85 self.case_flash_error_msg = False # 记录当前y用例是否更新了errorMsg 86 self.is_upload_method_result = False # 记录当前用例是否上报过第一个失败步骤 87 88 self.step_section = RunSection.SETUP # 用例方法setup/test/teardown 89 self.step_index = -1 # 当前步骤序号 90 self.step_error_msg = '' # 当前步骤的失败信息 91 self.step_fail_msg = '' # 用户指定的失败信息 92 self.step_result = RunResult.PASSED # 当前步骤执行结果 93 self.steps_info = [] # 记录测试用例(含测试套子用例)的步骤信息,如步骤名称、执行结果、耗时、截图等 94 self.suite_steps_info = [] # 记录测试套的的步骤信息,如步骤名称、执行结果、耗时、截图等 95 self.auto_record_steps_info = False # 默认记录记录用例操作步骤的信息,设为False,需人工调用record_step添加 96 97 self.test_method = TestMethod(self.log) 98 self.cur_check_cmd = CurCheckCmd() 99 100 # 失败截图相关 101 self.checkepr = False # 啥含义? 102 self.image_num = 0 103 self.video_num = 0 104 self.dump_xml_num = 0 105 106 # prepare相关 107 self.status = 0 108 self.description = '' # VAR.CurCase.Description memoryLeakReport.py用到 109 self.log_details_path = "./log/test_run_details.log" 110 self.log_path = "./log/test_run_summary.log" 111 self.report_path = '' 112 self.iperf_path = None # wifi相关 113 114 # windows 115 self.win_capture_path = '' # WinCapturePath 116 self.exact_start_time = '' # memoryLeakReport.py用到VAR.CurCase.ExactStartTime 117 self.start_time = '' # memoryLeakReport.py用到VAR.CurCase.StartTime 118 119 self.case_name_file_path = '' # VAR.CurCase.CaseName.FilePath 120 self.device_log = DeviceLog() 121 122 self.case_instance = None 123 self.suite_instance = None 124 125 self.devices = list() 126 self.is_capture_step_screen = False 127 128 @property 129 def testcase(self): 130 return self.case_instance 131 132 @property 133 def testsuite(self): 134 return self.suite_instance 135 136 def set_case_instance(self, case_obj): 137 self.case_instance = case_obj 138 self.set_capture_step_screen_flag(case_obj) 139 140 def set_suite_instance(self, suite_obj): 141 self.suite_instance = suite_obj 142 self.set_capture_step_screen_flag(suite_obj) 143 144 def set_capture_step_screen_flag(self, instance): 145 if instance is None: 146 return 147 configs = instance.configs 148 if configs.get("testargs", None) and configs["testargs"].get("screenshot", [""])[0].lower() == "true": 149 self.is_capture_step_screen = True 150 self.devices = instance.devices 151 152 def set_error_msg(self, error_msg): 153 self.log.debug("set CurCase error msg as: {}".format(error_msg)) 154 self.error_msg = error_msg 155 156 def set_run_section(self, run_section): 157 self.log.debug("set CurCase run section as: {}".format(run_section)) 158 self.run_section = run_section 159 160 def set_case_result(self, case_result): 161 self.case_result = case_result 162 self.log.debug( 163 "set CurCase case result as: {}".format(self.case_result)) 164 165 def set_step_total(self, step_total): 166 self.step_total = step_total 167 self.log.debug( 168 "set CurCase step total as: {}".format(self.step_total)) 169 170 def set_name(self, name): 171 self.name = name 172 self.log.debug("set CurCase name as: {}".format(self.name)) 173 174 def set_step_section(self, step_section): 175 self.step_section = step_section 176 self.log.debug( 177 "set CurCase step section as: {}".format(self.step_section)) 178 179 def set_case_screenshot_dir(self, test_suite_path, task_report_dir, cur_case_full_path): 180 case_screenshot_dir = task_report_dir if is_env_pool_run_mode() else os.path.join(task_report_dir, "script") 181 case_abs_path_base_name = get_base_name(cur_case_full_path, is_abs_name=True) 182 if case_abs_path_base_name and test_suite_path: 183 self.log.debug("case_abs_path_base_name:{}, test_suite_path:{}" 184 .format(case_abs_path_base_name, test_suite_path)) 185 _list = case_abs_path_base_name.split(test_suite_path) 186 if len(_list) == 2: 187 case_screenshot_dir = os.path.abspath( 188 os.path.join(task_report_dir, _list[1].strip(os.sep))) 189 self.case_screenshot_dir = case_screenshot_dir 190 self.log.debug("set case screenshot dir path as: {}".format( 191 self.case_screenshot_dir)) 192 193 def init_stage_var(self, test_method_name, 194 run_section=None, is_error_msg=False): 195 if run_section: 196 self.set_run_section(run_section) 197 self.test_method.init_test_method(test_method_name, 198 is_error_msg=is_error_msg) 199 200 def set_checkepr(self, checkepr): 201 self.checkepr = checkepr 202 self.log.debug("set project checkepr as: {}".format(self.checkepr)) 203 204 def flash_error_msg_and_result(self, error_msg): 205 if not self.error_msg: 206 self.set_error_msg(error_msg) 207 if not self.test_method.error_msg: 208 self.test_method.set_error_msg(error_msg) 209 if self.case_result == RunResult.PASSED: 210 self.set_case_result(RunResult.FAILED) 211 if self.test_method.result == RunResult.PASSED: 212 self.test_method.set_result(RunResult.FAILED) 213 214 def set_step_index(self, index): 215 self.step_index = index 216 217 def set_step_info(self, name, **kwargs): 218 # 不允许外部的同名参数修改内部的记录 219 for builtin_key in ["cost", "result", "screenshot", "_timestamp"]: 220 if builtin_key in kwargs: 221 kwargs.pop(builtin_key) 222 # 耗时为前后Step的记录时间差 223 steps_info = self._get_steps_info_obj() 224 index = len(steps_info) 225 log.info(f'<div class="aw" id="{index}">{name}</div>') 226 if index > 0: 227 last_step = steps_info[-1] 228 last_step["cost"] = round(time.time() - last_step.get("_timestamp"), 3) 229 shots = self._capture_step_screen(name) 230 step = {"name": name, "result": "pass", "cost": 0, "screenshot": shots, "_timestamp": time.time()} 231 step.update(kwargs) 232 steps_info.append(step) 233 self.set_step_index(index) 234 return index 235 236 def update_step_info(self, index, **kwargs): 237 steps_info = self._get_steps_info_obj() 238 max_index = len(steps_info) - 1 239 if not 0 <= index <= max_index: 240 log.warning(f"update step info failed, index must be in [0, {max_index}]") 241 return 242 step = steps_info[index] 243 step.update(kwargs) 244 245 def update_step_shots(self, path, link): 246 if path is None or not os.path.exists(path): 247 return 248 steps_info = self._get_steps_info_obj() 249 if len(steps_info) == 0: 250 return 251 save_name = os.path.basename(path) 252 steps_info[-1].get("screenshot", []).append(f'<a href="{link}" target="_blank">{save_name}</a>') 253 254 def get_steps_extra_head(self): 255 """获取步骤记录的额外表头字段(AZ order)""" 256 steps_info = self._get_steps_info_obj() 257 if len(steps_info) == 0: 258 return [] 259 # 默认表头,与self.set_step_info添加的记录对应 260 heads = ["name", "result", "cost", "screenshot"] 261 # 人工记录模式下,可拓展记录更多数据,获取最长的数据项的key做为表头 262 size, target = len(heads), None 263 for step in steps_info: 264 current_size = len(step) 265 if current_size >= size: 266 target = step 267 size = current_size 268 for name in target.keys(): 269 if name not in heads and not name.startswith("_"): 270 heads.append(name) 271 return sorted(heads[4:]) 272 273 def get_steps_info(self): 274 steps_info = self._get_steps_info_obj() 275 if len(steps_info) > 0: 276 last_step = steps_info[-1] 277 last_step["cost"] = round(time.time() - last_step.get("_timestamp"), 3) 278 return steps_info 279 280 def _get_steps_info_obj(self): 281 """返回测试套或测试用例的记录表""" 282 return self.steps_info if self.case_instance is not None else self.suite_steps_info 283 284 def _capture_step_screen(self, step_name): 285 """ 286 take a screenshot of each device after each step is performed 287 """ 288 shots = [] 289 if self.is_capture_step_screen: 290 from devicetest.controllers.tools.screen_agent import ScreenAgent 291 for device in self.devices: 292 path, link = ScreenAgent.capture_step_picture(device, step_name) 293 if path is None or not os.path.exists(path): 294 continue 295 save_name = os.path.basename(path) 296 shots.append(f'<a href="{link}" target="_blank">{save_name}</a>') 297 return shots 298 299 300class TestMethod: 301 def __init__(self, _log): 302 # 步骤级别参数 303 self.log = _log 304 self.name = 'setup' 305 self.result = RunResult.PASSED 306 self.level = '' 307 self.error_msg = '' 308 self.method_return = '' 309 self.func_ret = [] 310 self.step_flash_fail_msg = False 311 312 def set_result(self, result=None): 313 self.result = result or RunResult.PASSED 314 self.log.debug( 315 "set TestMethod result as: {}".format(self.result)) 316 317 def set_error_msg(self, error_msg): 318 self.error_msg = error_msg 319 self.log.debug( 320 "set TestMethod error msg as: {}".format(self.error_msg)) 321 322 def init_test_method(self, name, is_error_msg=False): 323 self.level = '', 324 self.name = name, 325 self.result = RunResult.PASSED 326 if is_error_msg: 327 self.error_msg = '' 328 self.func_ret.clear() 329 330 def init_aw_method(self): 331 self.error_msg = '' 332 self.result = RunResult.PASSED 333 self.step_flash_fail_msg = False # 记录当前步骤是否更新了failMsg 334 self.func_ret.clear() 335 self.log.debug("init aw method.") 336 337 338class CurStep: 339 pass 340 341 342class Prepare: 343 def __init__(self): 344 self.path = '' 345 self.config = {} 346 347 def set_prepare_path(self, path): 348 if path: 349 self.path = path 350 log.debug("prepare path:{}".format(path)) 351 352 353class Settings: 354 language = '' 355 product = '' # VAR.Settings.Product 356 357 358class Event: 359 configs = {} # VAR.Event.Configs 360 361 362class RedirectLog: 363 task_name = "" # VAR.Project.RedirectLog.TaskName 364 365 366class DeviceLog: 367 ftp_path = [] # VAR.CurCase.DeviceLog.FthPath 368 369 370class ProjectVariables: 371 def __init__(self, _log): 372 # 工程级别参数 373 self.log = _log 374 self.record = ProjectRecord(_log) 375 self.project_path = '' # xdevice工程路径 376 self.aw_path = '' # 测试套aw路径 377 self.testcase_path = '' 378 self.settings = None 379 self.resource_path = '' # 测试套工程resource路径 380 self.test_suite_path = '' # 测试套工程路径 381 self.task_report_dir = '' # 测试用例的框架日志路径 382 self.prepare = Prepare() # prepare 相关 383 self.cur_case_full_path = '' # 记录当前正执行用例全路径 384 self.execute_case_name = None # 记录当前正执行用例id 385 self.config_json = {} # 用户自定义的公共的参数 386 self.property_config = [] # 用户自定义的设备相关的参数 387 self.retry_test_list = [] 388 self.devicename = {} 389 self.step_debug = '' 390 391 self.monkey = False # extension/monkey/monkey.py VAR.Project.Monkey 392 self.task_id = "" # VAR.Project.TaskID memoryLeakReport.py中用到,先记录该字段 VAR。Projec.TaskId 393 self.settings = Settings() # target中用到:VAR。Settings.Language 394 self.total = 0 # VAR.Project.Total 395 self.start_time = '' # memoryLeakReport.py用到VAR.Project.StartTime 396 self.exact_start_time = '' # memoryLeakReport.py用到VAR.Project.ExactStartTime 397 self.finish = 0 # VAR.Project.Finish 398 399 self.config = { 400 } 401 402 self.event = Event() 403 self.test_file = '' # VAR.Project.TestFile 404 self.is_ticc_server = False # GlobalParam.IS_TICC_SERVER 405 self.redirect_log = RedirectLog() 406 407 def set_project_path(self, project_path=None): 408 self.project_path = project_path or get_project_path() 409 self.log.debug("project path is: {}".format(self.project_path)) 410 411 def set_aw_path(self, aw_path): 412 if aw_path: 413 self.aw_path = aw_path 414 self.log.debug("aw path is: {}".format(self.aw_path)) 415 416 def set_testcase_path(self, testcase_path): 417 self.testcase_path = testcase_path 418 self.log.debug("testcase path is: {}".format(self.testcase_path)) 419 420 def set_settings(self, settings): 421 if settings: 422 self.settings = settings 423 self.log.debug("settings is: {}".format(self.settings)) 424 425 def set_test_suite_path(self, test_suite_path): 426 if test_suite_path: 427 self.test_suite_path = test_suite_path 428 self.log.debug("test suite path is: {}".format(self.test_suite_path)) 429 430 def set_task_report_dir(self, task_report_dir): 431 if task_report_dir: 432 self.task_report_dir = task_report_dir 433 self.log.debug("task report dir: {}".format(self.task_report_dir)) 434 435 def set_resource_path(self, resource_path): 436 if resource_path: 437 self.resource_path = resource_path 438 self.log.debug("resource path is: {}".format(self.resource_path)) 439 440 def set_config_json(self, config_json): 441 self.config_json = config_json 442 self.log.debug("config json is: {}".format(self.config_json)) 443 444 def set_property_config(self, property_config): 445 self.property_config = property_config 446 self.log.debug("property config is: {}".format(self.property_config)) 447 448 def set_devicename(self, devicename): 449 if devicename: 450 self.devicename = devicename 451 self.log.debug("devicename is: {}".format(self.devicename)) 452 453 454class CurCheckCmd: 455 def __init__(self): 456 # 用例校验参数? 457 self.through = "" 458 self.expect = "" 459 self.actual = "" 460 461 def get_cur_check_status(self): 462 if all([self.through, self.expect, self.actual]): 463 return True 464 return False 465 466 def get_cur_check_msg(self): 467 return "{}, expect:{}, actual:{}".format(self.through, self.expect, 468 self.actual) 469 470 471class DeccVariable: 472 __cur_case = {} 473 __thread_lock = threading.Lock() 474 project = ProjectVariables(log) 475 476 @classmethod 477 def set_project_obj(cls, project_obj): 478 log.info("init DeccVariable project object") 479 cls.project = project_obj 480 481 @classmethod 482 def set_cur_case_obj(cls, cur_case_obj): 483 log.info("init DeccVariable cur case object") 484 with cls.__thread_lock: 485 cls.__cur_case[cls.__cur_case_key()] = cur_case_obj 486 487 @classmethod 488 def cur_case(cls): 489 with cls.__thread_lock: 490 return cls.__cur_case.get(cls.__cur_case_key(), None) 491 492 @classmethod 493 def __cur_case_key(cls): 494 return threading.current_thread().ident 495 496 @classmethod 497 def reset(cls): 498 log.info("reset DeccVariable") 499 with cls.__thread_lock: 500 key = cls.__cur_case_key() 501 if key in cls.__cur_case: 502 cls.__cur_case.pop(key) 503