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 json 20import os 21import re 22import stat 23import subprocess 24import time 25import zipfile 26from dataclasses import dataclass 27from multiprocessing import Process 28from multiprocessing import Queue 29from tempfile import TemporaryDirectory 30from tempfile import NamedTemporaryFile 31 32from xdevice import DeviceLabelType 33from xdevice import FilePermission 34from xdevice import ITestKit 35from xdevice import platform_logger 36from xdevice import Plugin 37from xdevice import ParamError 38from xdevice import get_file_absolute_path 39from xdevice import get_config_value 40from xdevice import exec_cmd 41from xdevice import ConfigConst 42from xdevice import AppInstallError 43from xdevice import convert_serial 44from xdevice import check_path_legal 45from xdevice import modify_props 46from xdevice import get_app_name_by_tool 47from xdevice import remount 48from xdevice import get_cst_time 49from xdevice import check_device_ohca 50from xdevice import Variables 51 52from ohos.constants import CKit 53from ohos.environment.dmlib import HdcHelper 54from ohos.environment.dmlib import CollectingOutputReceiver 55from ohos.error import ErrorMessage 56 57__all__ = ["STSKit", "CommandKit", "PushKit", "PropertyCheckKit", "ShellKit", 58 "ConfigKit", "AppInstallKit", "ComponentKit", 59 "PermissionKit", "SmartPerfKit", "CommonPushKit"] 60 61MAX_WAIT_COUNT = 4 62TARGET_SDK_VERSION = 22 63 64LOG = platform_logger("Kit") 65 66 67@Plugin(type=Plugin.TEST_KIT, id=CKit.command) 68class CommandKit(ITestKit): 69 70 def __init__(self): 71 self.run_command = [] 72 self.teardown_command = [] 73 self.paths = "" 74 75 def __check_config__(self, config): 76 self.paths = get_config_value('paths', config) 77 self.teardown_command = get_config_value('teardown', config) 78 self.run_command = get_config_value('shell', config) 79 80 def __setup__(self, device, **kwargs): 81 del kwargs 82 LOG.debug("CommandKit setup, device:{}, params:{}". 83 format(device, self.get_plugin_config().__dict__)) 84 if len(self.run_command) == 0: 85 LOG.info("No setup_command to run, skipping!") 86 return 87 for command in self.run_command: 88 self._run_command(command, device) 89 90 def __teardown__(self, device): 91 LOG.debug("CommandKit teardown: device:{}, params:{}".format 92 (device, self.get_plugin_config().__dict__)) 93 if len(self.teardown_command) == 0: 94 LOG.info("No teardown_command to run, skipping!") 95 return 96 for command in self.teardown_command: 97 self._run_command(command, device) 98 99 def _run_command(self, command, device): 100 101 command_type = command.get("name").strip() 102 command_value = command.get("value") 103 104 if command_type == "reboot": 105 device.reboot() 106 elif command_type == "install": 107 LOG.debug("Trying to install package {}".format(command_value)) 108 package = get_file_absolute_path(command_value, self.paths) 109 if not package or not os.path.exists(package): 110 LOG.error( 111 "The package {} to be installed does not exist".format( 112 package)) 113 114 result = device.install_package(package) 115 if not result.startswith("Success") and "successfully" not in result: 116 raise AppInstallError(ErrorMessage.Device.Code_0303007.format( 117 package, device.__get_serial__(), result)) 118 LOG.debug("Installed package finished {}".format(package)) 119 elif command_type == "uninstall": 120 LOG.debug("Trying to uninstall package {}".format(command_value)) 121 package = get_file_absolute_path(command_value, self.paths) 122 app_name = get_app_name_by_tool(package, self.paths) 123 if app_name: 124 result = device.uninstall_package(app_name) 125 if not result.startswith("Success"): 126 LOG.error("error uninstalling package %s %s" % 127 (device.__get_serial__(), result)) 128 LOG.debug("uninstall package finished {}".format(app_name)) 129 elif command_type == "pull": 130 files = command_value.split("->") 131 remote = files[0].strip() 132 local = files[1].strip() 133 device.pull_file(remote, local) 134 elif command_type == "push": 135 files = command_value.split("->") 136 if len(files) != 2: 137 LOG.error("The push spec is invalid: {}".format(command_value)) 138 return 139 src, dst = files[0].strip(), files[1].strip() 140 if not dst.startswith("/"): 141 dst = Props.dest_root + dst 142 LOG.debug("Trying to push the file local {} to remote{}".format(src, dst)) 143 real_src_path = get_file_absolute_path(src, self.paths) 144 if not real_src_path or not os.path.exists(real_src_path): 145 LOG.error( 146 "The src file {} to be pushed does not exist".format(src)) 147 device.push_file(real_src_path, dst) 148 LOG.debug("Push file finished from {} to {}".format(src, dst)) 149 elif command_type == "shell": 150 device.execute_shell_command(command_value) 151 152 153@Plugin(type=Plugin.TEST_KIT, id=CKit.sts) 154class STSKit(ITestKit): 155 def __init__(self): 156 self.sts_version = "" 157 self.throw_error = "" 158 159 def __check_config__(self, config): 160 self.sts_version = get_config_value('sts-version', config) 161 self.throw_error = get_config_value('throw-error', config) 162 if len(self.sts_version) < 1: 163 raise TypeError(ErrorMessage.Config.Code_0302013.format(self.sts_version)) 164 165 def __setup__(self, device, **kwargs): 166 del kwargs 167 LOG.debug("STSKit setup, device:{}, params:{}". 168 format(device, self.get_plugin_config().__dict__)) 169 device_spl = device.get_property(Props.security_patch) 170 if device_spl is None or device_spl == "": 171 err_msg = ErrorMessage.Config.Code_0302015.format(device_spl) 172 LOG.error(err_msg) 173 raise ParamError(err_msg) 174 rex = '^[a-zA-Z\\d\\.]+_([\\d]+-[\\d]+)$' 175 match = re.match(rex, self.sts_version) 176 if match is None: 177 err_msg = ErrorMessage.Config.Code_0302014.format(self.sts_version) 178 LOG.error(err_msg) 179 raise ParamError(err_msg) 180 sts_version_date_user = match.group(1).join("-01") 181 sts_version_date_kernel = match.group(1).join("-05") 182 if device_spl in [sts_version_date_user, sts_version_date_kernel]: 183 LOG.info( 184 "The device SPL version {} match the sts version {}".format( 185 device_spl, self.sts_version)) 186 else: 187 err_msg = ErrorMessage.Config.Code_0302016.format(device_spl, self.sts_version) 188 LOG.error(err_msg) 189 raise ParamError(err_msg) 190 191 def __teardown__(self, device): 192 LOG.debug("STSKit teardown: device:{}, params:{}".format 193 (device, self.get_plugin_config().__dict__)) 194 195 196class PushBase(ITestKit): 197 def __init__(self): 198 self.pre_push = "" 199 self.push_list = [] 200 self.post_push = "" 201 self.is_uninstall = "" 202 self.paths = "" 203 self.pushed_file = [] 204 self.abort_on_push_failure = True 205 self.teardown_push = "" 206 self.request = None 207 208 def __check_config__(self, config): 209 self.pre_push = get_config_value('pre-push', config) 210 self.push_list = get_config_value('push', config) 211 self.post_push = get_config_value('post-push', config) 212 self.teardown_push = get_config_value('teardown-push', config) 213 self.is_uninstall = get_config_value('uninstall', config, 214 is_list=False, default=True) 215 self.abort_on_push_failure = get_config_value( 216 'abort-on-push-failure', config, is_list=False, default=True) 217 if isinstance(self.abort_on_push_failure, str): 218 self.abort_on_push_failure = False if \ 219 self.abort_on_push_failure.lower() == "false" else True 220 221 self.paths = get_config_value('paths', config) 222 self.pushed_file = [] 223 224 def __setup__(self, device, **kwargs): 225 pass 226 227 def _get_push_list(self, device): 228 new_push_list = [] 229 if getattr(device, 'common_kits', None): 230 LOG.info(list(filter(lambda x: isinstance(x, CommonPushKit), device.common_kits))) 231 common_push_list = list(filter(lambda x: isinstance(x, CommonPushKit), device.common_kits))[0].push_list 232 for push_info in self.push_list: 233 if push_info in common_push_list: 234 continue 235 else: 236 new_push_list.append(push_info) 237 else: 238 new_push_list = self.push_list 239 return new_push_list 240 241 def _push_file(self, device, push_list: list) -> list: 242 dsts = [] 243 for push_info in push_list: 244 files = re.split('->|=>', push_info) 245 if len(files) != 2: 246 LOG.error("The push spec is invalid: {}".format(push_info)) 247 continue 248 src, dst = files[0].strip(), files[1].strip() 249 if not dst.startswith("/"): 250 dst = Props.dest_root + dst 251 LOG.debug("Trying to push the file local {} to remote {}".format(src, dst)) 252 253 try: 254 real_src_path = get_file_absolute_path(src, self.paths) 255 except ParamError as error: 256 real_src_path = self.__download_web_resource(device, src) 257 if real_src_path is None: 258 if self.abort_on_push_failure: 259 raise error 260 LOG.warning(error, error_no=error.error_no) 261 continue 262 if check_device_ohca(device) and dst.startswith("/data/"): 263 regex = re.compile('^/data/*') 264 dst = regex.sub("/data/ohos_data/", dst) 265 # hdc don't support push directory now 266 if os.path.isdir(real_src_path): 267 command = "shell mkdir {}".format(dst) 268 device.connector_command(command) 269 for root, _, files in os.walk(real_src_path): 270 for file in files: 271 device.push_file("{}".format(os.path.join(root, file)), 272 "{}".format(dst)) 273 LOG.debug( 274 "Push file finished from {} to {}".format( 275 os.path.join(root, file), dst)) 276 self.pushed_file.append(os.path.join(dst, file)) 277 else: 278 if device.is_directory(dst): 279 dst = os.path.join(dst, os.path.basename(real_src_path)) 280 if dst.find("\\") > -1: 281 dst_paths = dst.split("\\") 282 dst = "/".join(dst_paths) 283 device.push_file("{}".format(real_src_path), 284 "{}".format(dst)) 285 LOG.debug("Push file finished from {} to {}".format(src, dst)) 286 self.pushed_file.append(dst) 287 dsts.append(dst) 288 return dsts 289 290 def __download_web_resource(self, device, file_path): 291 """下载OpenHarmony兼容性测试资源文件""" 292 # 在命令行配置 293 config = Variables.config 294 request_config = self.request.config 295 enable_web_resource1 = config.taskargs.get(ConfigConst.enable_web_resource, "false").lower() 296 # 在xml配置 297 web_resource = config.resource.get(ConfigConst.web_resource, {}) 298 web_resource_url = web_resource.get(ConfigConst.tag_url, "").strip() 299 enable_web_resource2 = web_resource.get(ConfigConst.tag_enable, "false").lower() 300 if enable_web_resource1 == "false" and enable_web_resource2 == "false": 301 return None 302 file_path = file_path.replace("\\", "/") 303 # 必须是resource或./resource开头的资源文件 304 pattern = re.compile(r'^(\./)?resource/') 305 if re.match(pattern, file_path) is None: 306 LOG.warning("push file path does not start with resource") 307 return None 308 file_path = re.sub(pattern, "", file_path) 309 save_file = os.path.join( 310 request_config.get(ConfigConst.resource_path), file_path) 311 if os.path.exists(save_file): 312 return save_file 313 url = self.__query_resource_url(device, web_resource_url, "resource/" + file_path) 314 if not url: 315 return None 316 save_path = os.path.dirname(save_file) 317 if not os.path.exists(save_path): 318 os.makedirs(save_path) 319 cli = None 320 try: 321 import requests 322 cli = requests.get(url, timeout=5, verify=False) 323 if cli.status_code == 200: 324 file_fd = os.open(save_file, os.O_CREAT | os.O_WRONLY, FilePermission.mode_644) 325 with os.fdopen(file_fd, mode="wb+") as s_file: 326 for chunk in cli.iter_content(chunk_size=1024 * 4): 327 s_file.write(chunk) 328 s_file.flush() 329 except Exception as e: 330 LOG.error(f"download the resource of '{file_path}' failed. {e}") 331 finally: 332 if cli is not None: 333 cli.close() 334 return save_file if os.path.exists(save_file) else None 335 336 def __query_resource_url(self, device, query_url, file_path): 337 """获取资源下载链接""" 338 url = "" 339 os_type = "" 340 device_class = device.__class__.__name__ 341 if device_class == "Device": 342 os_type = "standard system" 343 if device_class == "DeviceLite" and device.label == DeviceLabelType.ipcamera: 344 os_type = "small system" 345 os_version = getattr(device, "device_props", {}).get("OsFullName", "") 346 task_name = self.request.config.get(ConfigConst.task, "") 347 module_name = self.request.get_module_name() 348 pattern = r'((?:AC|DC|HA|HI|SS)TS)\S*' 349 ret = re.match(pattern, task_name.upper()) or re.match(pattern, module_name.upper()) 350 test_type = ret.group(1) if ret else None 351 if not os_type or not os_version or not test_type: 352 LOG.warning("query resource params is none") 353 return url 354 cli = None 355 params = { 356 "filePath": file_path, 357 "testType": test_type, 358 "osType": os_type, 359 "osVersion": os_version 360 } 361 LOG.debug(f"query resource's params {params}") 362 try: 363 import requests 364 cli = requests.post(query_url, json=params, timeout=5, verify=False) 365 rsp_code = cli.status_code 366 rsp_body = cli.content.decode() 367 LOG.debug(f"query resource's response code {rsp_code}") 368 LOG.debug(f"query resource's response body {rsp_body}") 369 if rsp_code == 200: 370 try: 371 data = json.loads(rsp_body) 372 if data.get("code") == 200: 373 url = data.get("body") 374 else: 375 msg = data.get("msg") 376 LOG.error(f"query the resource of '{file_path}' downloading url failed. {msg}") 377 except ValueError: 378 LOG.error("query resource's response body is not json data") 379 except Exception as e: 380 LOG.error(f"query the resource of '{file_path}' downloading url failed. {e}") 381 finally: 382 if cli is not None: 383 cli.close() 384 return url 385 386 def add_pushed_dir(self, src, dst): 387 for root, _, files in os.walk(src): 388 for file_path in files: 389 self.pushed_file.append( 390 os.path.join(root, file_path).replace(src, dst)) 391 392 def __teardown__(self, device): 393 LOG.debug("PushKit teardown: device: {}".format(device.device_sn)) 394 for command in self.teardown_push: 395 run_command(device, command) 396 if self.is_uninstall: 397 remount(device) 398 for file_name in self.pushed_file: 399 LOG.debug("Trying to remove file {}".format(file_name)) 400 file_name = file_name.replace("\\", "/") 401 402 for _ in range( 403 Props.trying_remove_maximum_times): 404 collect_receiver = CollectingOutputReceiver() 405 file_name = check_path_legal(file_name) 406 device.execute_shell_command("rm -rf {}".format( 407 file_name), receiver=collect_receiver, 408 output_flag=False) 409 if not collect_receiver.output: 410 LOG.debug( 411 "Removed file {} successfully".format(file_name)) 412 break 413 else: 414 LOG.error("Removed file {} successfully". 415 format(collect_receiver.output)) 416 else: 417 LOG.error("Failed to remove file {}".format(file_name)) 418 419 def __add_pushed_file__(self, device, src, dst): 420 if device.is_directory(dst): 421 dst = dst + os.path.basename(src) if dst.endswith( 422 "/") else dst + "/" + os.path.basename(src) 423 self.pushed_file.append(dst) 424 425 def __add_dir_pushed_files__(self, device, src, dst): 426 if device.file_exist(device, dst): 427 for _, dirs, files in os.walk(src): 428 for file_path in files: 429 if dst.endswith("/"): 430 dst = "%s%s" % (dst, os.path.basename(file_path)) 431 else: 432 dst = "%s/%s" % (dst, os.path.basename(file_path)) 433 self.pushed_file.append(dst) 434 for dir_name in dirs: 435 self.__add_dir_pushed_files__(device, dir_name, dst) 436 else: 437 self.pushed_file.append(dst) 438 439 440@Plugin(type=Plugin.TEST_KIT, id=CKit.propertycheck) 441class PropertyCheckKit(ITestKit): 442 def __init__(self): 443 self.prop_name = "" 444 self.expected_value = "" 445 self.throw_error = "" 446 447 def __check_config__(self, config): 448 self.prop_name = get_config_value('property-name', config, 449 is_list=False) 450 self.expected_value = get_config_value('expected-value', config, 451 is_list=False) 452 self.throw_error = get_config_value('throw-error', config, 453 is_list=False) 454 455 def __setup__(self, device, **kwargs): 456 del kwargs 457 LOG.debug("PropertyCheckKit setup, device:{}".format(device.device_sn)) 458 if not self.prop_name: 459 LOG.warning("The option of property-name not setting") 460 return 461 prop_value = device.get_property(self.prop_name) 462 if not prop_value: 463 LOG.warning( 464 "The property {} not found on device, cannot check the value". 465 format(self.prop_name)) 466 return 467 468 if prop_value != self.expected_value: 469 err_msg = ErrorMessage.Config.Code_0302024.format(self.prop_name, prop_value, self.expected_value) 470 LOG.warning(err_msg) 471 if self.throw_error and self.throw_error.lower() == 'true': 472 raise Exception(err_msg) 473 474 @classmethod 475 def __teardown__(cls, device): 476 LOG.debug("PropertyCheckKit teardown: device:{}".format( 477 device.device_sn)) 478 479 480@Plugin(type=Plugin.TEST_KIT, id=CKit.shell) 481class ShellKit(ITestKit): 482 def __init__(self): 483 self.command_list = [] 484 self.tear_down_command = [] 485 self.paths = None 486 487 def __check_config__(self, config): 488 self.command_list = get_config_value('run-command', config) 489 self.tear_down_command = get_config_value('teardown-command', config) 490 self.tear_down_local_command = get_config_value('teardown-localcommand', config) 491 self.paths = get_config_value('paths', config) 492 493 def __setup__(self, device, **kwargs): 494 del kwargs 495 LOG.debug("ShellKit setup, device:{}".format(device.device_sn)) 496 if len(self.command_list) == 0: 497 LOG.info("No setup_command to run, skipping!") 498 return 499 for command in self.command_list: 500 run_command(device, command) 501 502 def __teardown__(self, device): 503 LOG.debug("ShellKit teardown: device:{}".format(device.device_sn)) 504 if len(self.tear_down_command) == 0: 505 LOG.info("No teardown_command to run, skipping!") 506 else: 507 for command in self.tear_down_command: 508 run_command(device, command) 509 if len(self.tear_down_local_command) == 0: 510 LOG.info("No teardown-localcommand to run, skipping!") 511 else: 512 for command in self.tear_down_local_command: 513 LOG.info("Run local command: {}".format(command)) 514 ret = exec_cmd(command, timeout=None) 515 LOG.info("command output: {}".format(ret)) 516 517 518@dataclass 519class Props: 520 @dataclass 521 class Paths: 522 system_build_prop_path = "/system/build.prop" 523 service_wifi_app_path = "tools/wifi/xDeviceService-wifi.apk" 524 525 dest_root = "/data/data/" 526 mnt_external_storage = "EXTERNAL_STORAGE" 527 trying_remove_maximum_times = 3 528 maximum_connect_wifi_times = 3 529 connect_wifi_cmd = "am instrument -e request \"{{module:Wifi, " \ 530 "method:connectWifiByCertificate, params:{{'certPath':" \ 531 "'{}}}'," \ 532 "'certPassword':'{}'," \ 533 "'wifiName':'{}'}}}}\" " \ 534 "-w com.xdeviceservice.service.plrdtest/com.xdeviceservice.service.MainInstrumentation" 535 security_patch = "ro.build.version.security_patch" 536 537 538@Plugin(type=Plugin.TEST_KIT, id=CKit.config) 539class ConfigKit(ITestKit): 540 def __init__(self): 541 self.is_connect_wifi = "" 542 self.is_disconnect_wifi = "" 543 self.min_external_store_space = "" 544 self.is_disable_dialing = "" 545 self.is_test_harness = "" 546 self.is_audio_silent = "" 547 self.is_disable_dalvik_verifier = "" 548 self.build_prop_list = "" 549 self.is_enable_hook = "" 550 self.cust_prop_file = "" 551 self.is_prop_changed = False 552 self.local_system_prop_file = "" 553 self.cust_props = "" 554 self.is_reboot_delay = "" 555 self.is_remount = "" 556 self.local_cust_prop_file = {} 557 558 def __check_config__(self, config): 559 self.is_connect_wifi = get_config_value('connect-wifi', config, 560 is_list=False, default=False) 561 self.is_disconnect_wifi = get_config_value( 562 'disconnect-wifi-after-test', config, is_list=False, default=True) 563 self.min_external_store_space = get_config_value( 564 'min-external-store-space', config) 565 self.is_disable_dialing = get_config_value('disable-dialing', config) 566 self.is_test_harness = get_config_value('set-test-harness', config) 567 self.is_audio_silent = get_config_value('audio-silent', config) 568 self.is_disable_dalvik_verifier = get_config_value( 569 'disable-dalvik-verifier', config) 570 self.build_prop_list = get_config_value('build-prop', config) 571 self.cust_prop_file = get_config_value('cust-prop-file', config) 572 self.cust_props = get_config_value('cust-prop', config) 573 self.is_enable_hook = get_config_value('enable-hook', config) 574 self.is_reboot_delay = get_config_value('reboot-delay', config) 575 self.is_remount = get_config_value('remount', config, default=True) 576 self.local_system_prop_file = NamedTemporaryFile(prefix='build', 577 suffix='.prop', 578 delete=False).name 579 580 def __setup__(self, device, **kwargs): 581 del kwargs 582 LOG.debug("ConfigKit setup, device:{}".format(device.device_sn)) 583 if self.is_remount: 584 remount(device) 585 self.is_prop_changed = self.modify_system_prop(device) 586 self.is_prop_changed = self.modify_cust_prop( 587 device) or self.is_prop_changed 588 589 keep_screen_on(device) 590 if self.is_enable_hook: 591 pass 592 if self.is_prop_changed: 593 device.reboot() 594 595 def __teardown__(self, device): 596 LOG.debug("ConfigKit teardown: device:{}".format(device.device_sn)) 597 if self.is_remount: 598 remount(device) 599 if self.is_prop_changed: 600 device.push_file(self.local_system_prop_file, 601 Props.Paths.system_build_prop_path) 602 device.execute_shell_command( 603 " ".join(["chmod 644", Props.Paths.system_build_prop_path])) 604 os.remove(self.local_system_prop_file) 605 606 for target_file, temp_file in self.local_cust_prop_file.items(): 607 device.push_file(temp_file, target_file) 608 device.execute_shell_command( 609 " ".join(["chmod 644", target_file])) 610 os.remove(temp_file) 611 612 def modify_system_prop(self, device): 613 prop_changed = False 614 new_props = {} 615 if self.is_disable_dialing: 616 new_props['ro.telephony.disable-call'] = 'true' 617 if self.is_test_harness: 618 new_props['ro.monkey'] = '1' 619 new_props['ro.test_harness'] = '1' 620 if self.is_audio_silent: 621 new_props['ro.audio.silent'] = '1' 622 if self.is_disable_dalvik_verifier: 623 new_props['dalvik.vm.dexopt-flags'] = 'v=n' 624 for prop in self.build_prop_list: 625 if prop is None or prop.find("=") < 0 or len(prop.split("=")) != 2: 626 LOG.warning("The build prop:{} not match the format " 627 "'key=value'".format(prop)) 628 continue 629 new_props[prop.split("=")[0]] = prop.split("=")[1] 630 if new_props: 631 prop_changed = modify_props(device, self.local_system_prop_file, 632 Props.Paths.system_build_prop_path, 633 new_props) 634 return prop_changed 635 636 def modify_cust_prop(self, device): 637 prop_changed = False 638 cust_files = {} 639 new_props = {} 640 for cust_prop_file in self.cust_prop_file: 641 # the correct format should be "CustName:/cust/prop/absolutepath" 642 if len(cust_prop_file.split(":")) != 2: 643 LOG.error( 644 "The value %s of option cust-prop-file is incorrect" % 645 cust_prop_file) 646 continue 647 cust_files[cust_prop_file.split(":")[0]] = \ 648 cust_prop_file.split(":")[1] 649 for prop in self.cust_props: 650 # the correct format should be "CustName:key=value" 651 prop_infos = re.split(r'[:|=]', prop) 652 if len(prop_infos) != 3: 653 LOG.error( 654 "The value {} of option cust-prop is incorrect".format( 655 prop)) 656 continue 657 file_name, key, value = prop_infos 658 if file_name not in cust_files: 659 LOG.error( 660 "The custName {} must be in cust-prop-file option".format( 661 file_name)) 662 continue 663 props = new_props.setdefault(file_name, {}) 664 props[key] = value 665 666 for name in new_props.keys(): 667 cust_file = cust_files.get(name) 668 temp_cust_file = NamedTemporaryFile(prefix='cust', suffix='.prop', 669 delete=False).name 670 self.local_cust_prop_file[cust_file] = temp_cust_file 671 try: 672 prop_changed = modify_props(device, temp_cust_file, cust_file, 673 new_props[name]) or prop_changed 674 except KeyError: 675 LOG.error("Get props error.") 676 continue 677 678 return prop_changed 679 680 681@Plugin(type=Plugin.TEST_KIT, id=CKit.app_install) 682class AppInstallKit(ITestKit): 683 def __init__(self): 684 self.app_list = "" 685 self.app_list_name = "" 686 self.is_clean = "" 687 self.alt_dir = "" 688 self.ex_args = "" 689 self.installed_app = set() 690 self.paths = "" 691 self.is_pri_app = "" 692 self.pushed_hap_file = set() 693 self.env_index_list = None 694 695 def __check_config__(self, options): 696 self.app_list = get_config_value('test-file-name', options) 697 self.app_list_name = get_config_value('test-file-packName', options) 698 self.is_clean = get_config_value('cleanup-apps', options, False) 699 self.alt_dir = get_config_value('alt-dir', options, False) 700 if self.alt_dir and self.alt_dir.startswith("resource/"): 701 self.alt_dir = self.alt_dir[len("resource/"):] 702 self.ex_args = get_config_value('install-arg', options, False) 703 self.installed_app = set() 704 self.paths = get_config_value('paths', options) 705 self.is_pri_app = get_config_value('install-as-privapp', options, 706 False, default=False) 707 self.env_index_list = get_config_value('env-index', options) 708 709 def __setup__(self, device, **kwargs): 710 del kwargs 711 LOG.debug("AppInstallKit setup, device:{}".format(device.device_sn)) 712 if len(self.app_list) == 0: 713 LOG.info("No app to install, skipping!") 714 return 715 # to disable app install alert 716 for app in self.app_list: 717 if self.alt_dir: 718 app_file = get_file_absolute_path(app, self.paths, 719 self.alt_dir) 720 else: 721 app_file = get_file_absolute_path(app, self.paths) 722 if app_file is None: 723 LOG.error("The app file {} does not exist".format(app)) 724 continue 725 self.install_hap(device, app_file) 726 self.installed_app.add(app_file) 727 728 def __teardown__(self, device): 729 LOG.debug("AppInstallKit teardown: device:{}".format(device.device_sn)) 730 if self.is_clean and str(self.is_clean).lower() == "true": 731 if self.app_list_name and len(self.app_list_name) > 0: 732 for app_name in self.app_list_name: 733 result = device.uninstall_package(app_name) 734 if result and (result.startswith("Success") or "successfully" in result): 735 LOG.debug("uninstalling package Success. result is %s" % 736 result) 737 else: 738 LOG.warning("Error uninstalling package %s %s" % 739 (device.__get_serial__(), result)) 740 else: 741 for app in self.installed_app: 742 app_name = get_app_name(app) 743 if app_name: 744 result = device.uninstall_package(app_name) 745 if result and (result.startswith("Success") or "successfully" in result): 746 LOG.debug("uninstalling package Success. result is %s" % 747 result) 748 else: 749 LOG.warning("Error uninstalling package %s %s" % 750 (device.__get_serial__(), result)) 751 else: 752 LOG.warning("Can't find app name for %s" % app) 753 if self.is_pri_app: 754 remount(device) 755 for pushed_file in self.pushed_hap_file: 756 device.execute_shell_command("rm -r %s" % pushed_file) 757 758 def install_hap(self, device, hap_file): 759 if self.is_pri_app: 760 LOG.info("Install hap as privileged app {}".format(hap_file)) 761 hap_name = os.path.basename(hap_file).replace(".hap", "") 762 try: 763 with TemporaryDirectory(prefix=hap_name) as temp_dir: 764 zif_file = zipfile.ZipFile(hap_file) 765 zif_file.extractall(path=temp_dir) 766 entry_app = os.path.join(temp_dir, "Entry.app") 767 push_dest_dir = os.path.join("/system/priv-app/", hap_name) 768 device.execute_shell_command("rm -rf " + push_dest_dir, 769 output_flag=False) 770 device.push_file(entry_app, os.path.join( 771 push_dest_dir + os.path.basename(entry_app))) 772 device.push_file(hap_file, os.path.join( 773 push_dest_dir + os.path.basename(hap_file))) 774 self.pushed_hap_file.add(os.path.join( 775 push_dest_dir + os.path.basename(hap_file))) 776 device.reboot() 777 except Exception as exception: 778 err_msg = ErrorMessage.Device.Code_0303006.format(exception) 779 LOG.error(err_msg) 780 raise Exception(err_msg) from exception 781 finally: 782 zif_file.close() 783 else: 784 if hasattr(device, "is_oh"): 785 push_dest = "/data/local/tmp" 786 else: 787 push_dest = "/sdcard" 788 push_dest = "{}/{}".format(push_dest, os.path.basename(hap_file)) 789 device.push_file(hap_file, push_dest) 790 self.pushed_hap_file.add(push_dest) 791 output = device.execute_shell_command("bm install -p {} {}".format(push_dest, self.ex_args)) 792 if not output.startswith("Success") and "successfully" not in output: 793 output = output.strip() 794 if "[ERROR_GET_BUNDLE_INSTALLER_FAILED]" not in output.upper(): 795 raise AppInstallError(ErrorMessage.Device.Code_0303007.format( 796 push_dest, device.__get_serial__(), output)) 797 else: 798 LOG.info("'[ERROR_GET_BUNDLE_INSTALLER_FAILED]' occurs, " 799 "retry install hap") 800 exec_out = self.retry_install_hap( 801 device, "bm install -p {} {}".format(push_dest, self.ex_args)) 802 if not exec_out.startswith("Success") and "successfully" not in output: 803 raise AppInstallError(ErrorMessage.Device.Code_0303007.format( 804 push_dest, device.__get_serial__(), exec_out)) 805 else: 806 LOG.debug("Install %s success" % push_dest) 807 808 @classmethod 809 def retry_install_hap(cls, device, command): 810 real_command = [HdcHelper.CONNECTOR_NAME, "-t", str(device.device_sn), "-s", 811 "tcp:%s:%s" % (str(device.host), str(device.port)), 812 "shell", command] 813 message = "%s execute command: %s" % \ 814 (convert_serial(device.device_sn), " ".join(real_command)) 815 LOG.info(message) 816 exec_out = "" 817 for wait_count in range(1, MAX_WAIT_COUNT): 818 LOG.debug("Retry times:%s, wait %ss" % 819 (wait_count, (wait_count * 10))) 820 time.sleep(wait_count * 10) 821 exec_out = exec_cmd(real_command) 822 if exec_out and exec_out.startswith("Success"): 823 break 824 if not exec_out: 825 exec_out = "System is not in %s" % ["Windows", "Linux", "Darwin"] 826 LOG.info("Retry install hap result is: [%s]" % exec_out.strip()) 827 return exec_out 828 829 830@Plugin(type=Plugin.TEST_KIT, id=CKit.component) 831class ComponentKit(ITestKit): 832 833 def __init__(self): 834 self._white_list_file = "" 835 self._white_list = "" 836 self._cap_file = "" 837 self.paths = "" 838 self.cache_subsystem = set() 839 self.cache_part = set() 840 841 def __check_config__(self, config): 842 self._white_list_file = \ 843 get_config_value('white-list', config, is_list=False) 844 self._cap_file = get_config_value('cap-file', config, is_list=False) 845 self.paths = get_config_value('paths', config) 846 847 def __setup__(self, device, **kwargs): 848 if hasattr(device, ConfigConst.support_component): 849 return 850 if device.label in ["phone", "watch", "car", "tv", "tablet", "ivi"]: 851 command = "cat %s" % self._cap_file 852 result = device.execute_shell_command(command) 853 part_set = set() 854 subsystem_set = set() 855 if "{" in result: 856 for item in json.loads(result).get("components", []): 857 part_set.add(item.get("component", "")) 858 subsystems, parts = self.get_white_list() 859 part_set.update(parts) 860 subsystem_set.update(subsystems) 861 setattr(device, ConfigConst.support_component, 862 (subsystem_set, part_set)) 863 self.cache_subsystem.update(subsystem_set) 864 self.cache_part.update(part_set) 865 866 def get_cache(self): 867 return self.cache_subsystem, self.cache_part 868 869 def get_white_list(self): 870 if not self._white_list and self._white_list_file: 871 self._white_list = self._parse_white_list() 872 return self._white_list 873 874 def _parse_white_list(self): 875 subsystem = set() 876 part = set() 877 white_json_file = os.path.normpath(self._white_list_file) 878 if not os.path.isabs(white_json_file): 879 white_json_file = \ 880 get_file_absolute_path(white_json_file, self.paths) 881 if os.path.isfile(white_json_file): 882 subsystem_list = list() 883 flags = os.O_RDONLY 884 modes = stat.S_IWUSR | stat.S_IRUSR 885 with os.fdopen(os.open(white_json_file, flags, modes), 886 "r") as file_content: 887 json_result = json.load(file_content) 888 if "subsystems" in json_result.keys(): 889 subsystem_list.extend(json_result["subsystems"]) 890 for subsystem_item_list in subsystem_list: 891 for key, value in subsystem_item_list.items(): 892 if key == "subsystem": 893 subsystem.add(value) 894 elif key == "components": 895 for component_item in value: 896 if "component" in component_item.keys(): 897 part.add( 898 component_item["component"]) 899 900 return subsystem, part 901 902 def __teardown__(self, device): 903 if hasattr(device, ConfigConst.support_component): 904 setattr(device, ConfigConst.support_component, None) 905 self._white_list_file = "" 906 self._white_list = "" 907 self._cap_file = "" 908 self.cache_subsystem.clear() 909 self.cache_part.clear() 910 self.cache_device.clear() 911 912 913@Plugin(type=Plugin.TEST_KIT, id=CKit.permission) 914class PermissionKit(ITestKit): 915 def __init__(self): 916 self.package_name_list = None 917 self.permission_list = None 918 919 def __check_config__(self, config): 920 self.package_name_list = \ 921 get_config_value('package-names', config, True, []) 922 self.permission_list = \ 923 get_config_value('permissions', config, True, []) 924 925 def __setup__(self, device, **kwargs): 926 if not self.package_name_list or not self.permission_list: 927 LOG.warning("Please check parameters of permission kit in json") 928 return 929 for index in range(len(self.package_name_list)): 930 cur_name = self.package_name_list[index] 931 token_id = self._get_token_id(device, cur_name) 932 if not token_id: 933 LOG.warning("Not found accessTokenId of '{}'".format(cur_name)) 934 continue 935 for permission in self.permission_list[index]: 936 command = "atm perm -g -i {} -p {}".format(token_id, 937 permission) 938 out = device.execute_shell_command(command) 939 LOG.debug("Set permission result: {}".format(out)) 940 941 def __teardown__(self, device): 942 pass 943 944 def _get_token_id(self, device, pkg_name): 945 # shell bm dump -n 946 dum_command = "bm dump -n {}".format(pkg_name) 947 content = device.execute_shell_command(dum_command) 948 if not content or not str(content).startswith(pkg_name): 949 return "" 950 content = content[len(pkg_name) + len(":\n"):] 951 dump_dict = json.loads(content) 952 if "userInfo" not in dump_dict.keys(): 953 return "" 954 user_info_dict = dump_dict["userInfo"][0] 955 if "accessTokenId" not in user_info_dict.keys(): 956 return "" 957 else: 958 return user_info_dict["accessTokenId"] 959 960 961def keep_screen_on(device): 962 device.execute_shell_command("svc power stayon true") 963 964 965def run_command(device, command): 966 LOG.debug("The command:{} is running".format(command)) 967 stdout = None 968 if command.strip() == "remount": 969 remount(device) 970 elif command.strip() == "target mount": 971 device.connector_command(command.split(" ")) 972 elif command.strip() == "reboot": 973 device.reboot() 974 elif command.strip() == "reboot-delay": 975 pass 976 elif command.strip().endswith("&"): 977 device.execute_shell_in_daemon(command.strip()) 978 elif command.strip().startswith("push"): 979 if check_device_ohca(device): 980 command_list = command.split(" ") 981 if command_list and \ 982 len(command_list) > 0 and \ 983 command_list[len(command_list) - 1].startswith("/data/"): 984 regex = re.compile('^/data/*') 985 command_list[len(command_list) - 1] = regex.sub("/data/ohos_data/", 986 command_list[len(command_list) - 1]) 987 command = " ".join(command_list) 988 stdout = device.execute_shell_command(command) 989 elif command.strip().startswith("cp"): 990 if check_device_ohca(device): 991 regex = re.compile('^/data/*') 992 command_list = command.split(" ") 993 if command_list[1].startswith("/data/"): 994 command_list[1] = regex.sub("/data/ohos_data/", command_list[1]) 995 if command_list[2].startswith("/data"): 996 command_list[2] = regex.sub("/data/ohos_data/", command_list[2]) 997 command = " ".join(command_list) 998 stdout = device.execute_shell_command(command) 999 else: 1000 stdout = device.execute_shell_command(command) 1001 LOG.debug("Run command result: %s" % (stdout if stdout else "")) 1002 return stdout 1003 1004 1005def get_app_name(hap_app): 1006 hap_name = os.path.basename(hap_app).replace(".hap", "") 1007 app_name = "" 1008 hap_file_info = None 1009 config_json_file = "" 1010 try: 1011 hap_file_info = zipfile.ZipFile(hap_app) 1012 name_list = ["module.json", "config.json"] 1013 for _, name in enumerate(hap_file_info.namelist()): 1014 if name in name_list: 1015 config_json_file = name 1016 break 1017 config_info = hap_file_info.read(config_json_file).decode('utf-8') 1018 attrs = json.loads(config_info) 1019 if "app" in attrs.keys() and \ 1020 "bundleName" in attrs.get("app", dict()).keys(): 1021 app_name = attrs["app"]["bundleName"] 1022 LOG.info("Obtain the app name {} from json " 1023 "successfully".format(app_name)) 1024 else: 1025 LOG.debug("Tip: 'app' or 'bundleName' not " 1026 "in {}.hap/config.json".format(hap_name)) 1027 except Exception as e: 1028 LOG.error("get app name from hap error: {}".format(e)) 1029 finally: 1030 if hap_file_info: 1031 hap_file_info.close() 1032 return app_name 1033 1034 1035@Plugin(type=Plugin.TEST_KIT, id=CKit.smartperf) 1036class SmartPerfKit(ITestKit): 1037 def __init__(self): 1038 self._run_command = ["SP_daemon", "-PKG"] 1039 self._process = None 1040 self._pattern = "order:\\d+ (.+)=(.+)" 1041 self._param_key = ["cpu", "gpu", "ddr", "fps", "pnow", "ram", "temp"] 1042 self.target_name = "" 1043 self._msg_queue = None 1044 1045 def __check_config__(self, config): 1046 self._run_command.append(self.target_name) 1047 if isinstance(config, str): 1048 key_value_pairs = str(config).split(";") 1049 for key_value_pair in key_value_pairs: 1050 key, value = key_value_pair.split(":", 1) 1051 if key == "num": 1052 self._run_command.append("-N") 1053 self._run_command.append(value) 1054 else: 1055 if key in self._param_key and value == "true": 1056 self._run_command.append("-" + key[:1]) 1057 elif isinstance(config, dict): 1058 self._run_command[-1] = config.get('bundle_name', '') 1059 params = config.get('params', {}) 1060 if 'num' not in params: 1061 self._run_command.append("-N") 1062 self._run_command.append("1") 1063 LOG.warning(f'Set num to default value (1) because it is not set. ') 1064 for key, value in params.items(): 1065 if key == "num": 1066 self._run_command.append("-N") 1067 try: 1068 value = int(value) 1069 except ValueError as e: 1070 value = 1 1071 LOG.warning(f'Set num to default value ({value}) because: {e}') 1072 if value <= 0: 1073 value = 1 1074 LOG.warning(f'Set num to default value ({value}) because it cannot be less than 1! ') 1075 self._run_command.append(f"{value}") 1076 else: 1077 if key in self._param_key and value: 1078 self._run_command.append("-" + key[:1]) 1079 1080 def _execute(self, msg_queue, cmd_list, xls_file, proc_name): 1081 data = [] 1082 while msg_queue.empty(): 1083 process = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, 1084 stderr=subprocess.PIPE, 1085 shell=False) 1086 rev = process.stdout.read() 1087 data.append((get_cst_time().strftime("%Y-%m-%d-%H-%M-%S"), rev)) 1088 self.write_to_file(data, proc_name, xls_file) 1089 1090 def write_to_file(self, data, proc_name, xls_file): 1091 from openpyxl import Workbook 1092 from openpyxl import styles 1093 book = Workbook() 1094 sheet = book.active 1095 sheet.row_dimensions[1].height = 30 1096 sheet.column_dimensions["A"].width = 30 1097 sheet.sheet_properties.tabColor = "1072BA" 1098 alignment = styles.Alignment(horizontal='center', 1099 vertical='center') 1100 font = styles.Font(size=15, color="000000", 1101 bold=True, italic=False, strike=None, 1102 underline=None) 1103 names = ["time", "PKG"] 1104 start = True 1105 for _time, content in data: 1106 cur = [_time, proc_name] 1107 rev_list = str(content, "utf-8").split("\n") 1108 if start: 1109 start = False 1110 for rev in rev_list: 1111 result = re.match(self._pattern, rev) 1112 if result and result.group(1): 1113 names.append(result.group(1)) 1114 cur.append(result.group(2)) 1115 sheet.append(names) 1116 sheet.append(cur) 1117 for pos in range(1, len(names) + 1): 1118 cur_cell = sheet.cell(1, pos) 1119 sheet.column_dimensions[cur_cell.column_letter].width = 20 1120 cur_cell.alignment = alignment 1121 cur_cell.font = font 1122 else: 1123 for rev in rev_list: 1124 result = re.match(self._pattern, rev) 1125 if result and result.group(1): 1126 cur.append(result.group(2)) 1127 sheet.append(cur) 1128 book.save(xls_file) 1129 1130 def __setup__(self, device, **kwargs): 1131 request = kwargs.get("request") 1132 folder = os.path.join(request.get_config().report_path, "smart_perf") 1133 if not os.path.exists(folder): 1134 os.mkdir(folder) 1135 file = os.path.join(folder, "{}.xlsx".format(request.get_module_name())) 1136 if device.host != "127.0.0.1": 1137 cmd_list = [HdcHelper.CONNECTOR_NAME, "-s", 1138 "{}:{}".format(device.host, device.port), "-t", 1139 device.device_sn, "shell"] 1140 else: 1141 cmd_list = [HdcHelper.CONNECTOR_NAME, "-t", device.device_sn, 1142 "shell"] 1143 1144 cmd_list.extend(self._run_command) 1145 LOG.debug("Smart perf command:{}".format(" ".join(cmd_list))) 1146 self._msg_queue = Queue() 1147 self._process = Process(target=self._execute, args=( 1148 self._msg_queue, cmd_list, file, self.target_name)) 1149 self._process.start() 1150 1151 def __teardown__(self, device): 1152 if self._process: 1153 if self._msg_queue: 1154 self._msg_queue.put("") 1155 self._msg_queue = None 1156 else: 1157 self._process.terminate() 1158 self._process = None 1159 1160 1161@Plugin(type=Plugin.TEST_KIT, id=CKit.push) 1162class PushKit(PushBase): 1163 1164 def __setup__(self, device, **kwargs): 1165 self.request = kwargs.get("request") 1166 LOG.debug("PushKit setup, device: {}".format(device.device_sn)) 1167 for command in self.pre_push: 1168 run_command(device, command) 1169 remount(device) 1170 push_list = self._get_push_list(device) 1171 dsts = self._push_file(device, push_list) 1172 for command in self.post_push: 1173 run_command(device, command) 1174 return self.pushed_file, dsts 1175 1176 1177@Plugin(type=Plugin.TEST_KIT, id=CKit.common_push) 1178class CommonPushKit(PushBase): 1179 1180 def __setup__(self, device, **kwargs): 1181 self.request = kwargs.get("request") 1182 LOG.debug("Common PushKit setup, device: {}".format(device.device_sn)) 1183 for command in self.pre_push: 1184 run_command(device, command) 1185 remount(device) 1186 dsts = self._push_file(device, self.push_list) 1187 for command in self.post_push: 1188 run_command(device, command) 1189 return self.pushed_file, dsts 1190