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