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