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 time 21import json 22import stat 23 24from xdevice import ConfigConst 25from xdevice import ParamError 26from xdevice import IDriver 27from xdevice import platform_logger 28from xdevice import Plugin 29from xdevice import get_plugin 30from xdevice import JsonParser 31from xdevice import ShellHandler 32from xdevice import TestDescription 33from xdevice import get_device_log_file 34from xdevice import check_result_report 35from xdevice import get_kit_instances 36from xdevice import get_config_value 37from xdevice import do_module_kit_setup 38from xdevice import do_module_kit_teardown 39from xdevice import DeviceTestType 40from xdevice import CommonParserType 41from xdevice import FilePermission 42 43from ohos.testkit.kit import oh_jsunit_para_parse 44from ohos.executor.listener import CollectingPassListener 45 46__all__ = ["OHJSUnitTestDriver", "OHKernelTestDriver"] 47 48TIME_OUT = 300 * 1000 49 50LOG = platform_logger("OpenHarmony") 51 52 53@Plugin(type=Plugin.DRIVER, id=DeviceTestType.oh_kernel_test) 54class OHKernelTestDriver(IDriver): 55 """ 56 OpenHarmonyKernelTest 57 """ 58 def __init__(self): 59 self.timeout = 30 * 1000 60 self.result = "" 61 self.error_message = "" 62 self.kits = [] 63 self.config = None 64 self.runner = None 65 # log 66 self.device_log = None 67 self.hilog = None 68 self.log_proc = None 69 self.hilog_proc = None 70 71 def __check_environment__(self, device_options): 72 pass 73 74 def __check_config__(self, config): 75 pass 76 77 def __execute__(self, request): 78 try: 79 LOG.debug("Start to Execute OpenHarmony Kernel Test") 80 81 self.config = request.config 82 self.config.device = request.config.environment.devices[0] 83 84 config_file = request.root.source.config_file 85 86 self.result = "%s.xml" % \ 87 os.path.join(request.config.report_path, 88 "result", request.get_module_name()) 89 self.device_log = get_device_log_file( 90 request.config.report_path, 91 request.config.device.__get_serial__(), 92 "device_log") 93 94 self.hilog = get_device_log_file( 95 request.config.report_path, 96 request.config.device.__get_serial__(), 97 "device_hilog") 98 99 device_log_open = os.open(self.device_log, os.O_WRONLY | os.O_CREAT | 100 os.O_APPEND, FilePermission.mode_755) 101 hilog_open = os.open(self.hilog, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 102 FilePermission.mode_755) 103 self.config.device.device_log_collector.add_log_address(self.device_log, self.hilog) 104 with os.fdopen(device_log_open, "a") as log_file_pipe, \ 105 os.fdopen(hilog_open, "a") as hilog_file_pipe: 106 self.log_proc, self.hilog_proc = self.config.device.device_log_collector.\ 107 start_catch_device_log(log_file_pipe, hilog_file_pipe) 108 self._run_oh_kernel(config_file, request.listeners, request) 109 log_file_pipe.flush() 110 hilog_file_pipe.flush() 111 except Exception as exception: 112 self.error_message = exception 113 if not getattr(exception, "error_no", ""): 114 setattr(exception, "error_no", "03409") 115 LOG.exception(self.error_message, exc_info=False, error_no="03409") 116 raise exception 117 finally: 118 do_module_kit_teardown(request) 119 self.config.device.device_log_collector.remove_log_address(self.device_log, self.hilog) 120 self.config.device.device_log_collector.stop_catch_device_log(self.log_proc) 121 self.config.device.device_log_collector.stop_catch_device_log(self.hilog_proc) 122 self.result = check_result_report( 123 request.config.report_path, self.result, self.error_message) 124 125 def _run_oh_kernel(self, config_file, listeners=None, request=None): 126 try: 127 json_config = JsonParser(config_file) 128 kits = get_kit_instances(json_config, self.config.resource_path, 129 self.config.testcases_path) 130 self._get_driver_config(json_config) 131 do_module_kit_setup(request, kits) 132 self.runner = OHKernelTestRunner(self.config) 133 self.runner.suite_name = request.get_module_name() 134 self.runner.run(listeners) 135 finally: 136 do_module_kit_teardown(request) 137 138 def _get_driver_config(self, json_config): 139 target_test_path = get_config_value('native-test-device-path', 140 json_config.get_driver(), False) 141 test_suite_name = get_config_value('test-suite-name', 142 json_config.get_driver(), False) 143 test_suites_list = get_config_value('test-suites-list', 144 json_config.get_driver(), False) 145 timeout_limit = get_config_value('timeout-limit', 146 json_config.get_driver(), False) 147 conf_file = get_config_value('conf-file', 148 json_config.get_driver(), False) 149 self.config.arg_list = {} 150 if target_test_path: 151 self.config.target_test_path = target_test_path 152 if test_suite_name: 153 self.config.arg_list["test-suite-name"] = test_suite_name 154 if test_suites_list: 155 self.config.arg_list["test-suites-list"] = test_suites_list 156 if timeout_limit: 157 self.config.arg_list["timeout-limit"] = timeout_limit 158 if conf_file: 159 self.config.arg_list["conf-file"] = conf_file 160 timeout_config = get_config_value('shell-timeout', 161 json_config.get_driver(), False) 162 if timeout_config: 163 self.config.timeout = int(timeout_config) 164 else: 165 self.config.timeout = TIME_OUT 166 167 def __result__(self): 168 return self.result if os.path.exists(self.result) else "" 169 170 171class OHKernelTestRunner: 172 def __init__(self, config): 173 self.suite_name = None 174 self.config = config 175 self.arg_list = config.arg_list 176 177 def run(self, listeners): 178 handler = self._get_shell_handler(listeners) 179 # hdc shell cd /data/local/tmp/OH_kernel_test; 180 # sh runtest test -t OpenHarmony_RK3568_config 181 # -n OpenHarmony_RK3568_skiptest -l 60 182 command = "cd %s; chmod +x *; sh runtest test %s" % ( 183 self.config.target_test_path, self.get_args_command()) 184 self.config.device.execute_shell_command( 185 command, timeout=self.config.timeout, receiver=handler, retry=0) 186 187 def _get_shell_handler(self, listeners): 188 parsers = get_plugin(Plugin.PARSER, CommonParserType.oh_kernel_test) 189 if parsers: 190 parsers = parsers[:1] 191 parser_instances = [] 192 for parser in parsers: 193 parser_instance = parser.__class__() 194 parser_instance.suites_name = self.suite_name 195 parser_instance.listeners = listeners 196 parser_instances.append(parser_instance) 197 handler = ShellHandler(parser_instances) 198 return handler 199 200 def get_args_command(self): 201 args_commands = "" 202 for key, value in self.arg_list.items(): 203 if key == "test-suite-name" or key == "test-suites-list": 204 args_commands = "%s -t %s" % (args_commands, value) 205 elif key == "conf-file": 206 args_commands = "%s -n %s" % (args_commands, value) 207 elif key == "timeout-limit": 208 args_commands = "%s -l %s" % (args_commands, value) 209 return args_commands 210 211 212@Plugin(type=Plugin.DRIVER, id=DeviceTestType.oh_jsunit_test) 213class OHJSUnitTestDriver(IDriver): 214 """ 215 OHJSUnitTestDriver is a Test that runs a native test package on 216 given device. 217 """ 218 219 def __init__(self): 220 self.timeout = 80 * 1000 221 self.start_time = None 222 self.result = "" 223 self.error_message = "" 224 self.kits = [] 225 self.config = None 226 self.runner = None 227 self.rerun = True 228 self.rerun_all = True 229 # log 230 self.device_log = None 231 self.hilog = None 232 self.log_proc = None 233 self.hilog_proc = None 234 235 def __check_environment__(self, device_options): 236 pass 237 238 def __check_config__(self, config): 239 pass 240 241 def __execute__(self, request): 242 try: 243 LOG.debug("Start execute OpenHarmony JSUnitTest") 244 self.result = os.path.join( 245 request.config.report_path, "result", 246 '.'.join((request.get_module_name(), "xml"))) 247 self.config = request.config 248 self.config.device = request.config.environment.devices[0] 249 250 config_file = request.root.source.config_file 251 suite_file = request.root.source.source_file 252 253 if not suite_file: 254 raise ParamError( 255 "test source '%s' not exists" % 256 request.root.source.source_string, error_no="00110") 257 LOG.debug("Test case file path: %s" % suite_file) 258 self.config.device.set_device_report_path(request.config.report_path) 259 self.hilog = get_device_log_file(request.config.report_path, 260 request.config.device.__get_serial__() + "_" + request. 261 get_module_name(), 262 "device_hilog") 263 264 hilog_open = os.open(self.hilog, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 265 0o755) 266 self.config.device.device_log_collector.add_log_address(self.device_log, self.hilog) 267 self.config.device.execute_shell_command(command="hilog -r") 268 with os.fdopen(hilog_open, "a") as hilog_file_pipe: 269 if hasattr(self.config, "device_log") \ 270 and self.config.device_log == ConfigConst.device_log_on \ 271 and hasattr(self.config.device, "clear_crash_log"): 272 self.config.device.device_log_collector.clear_crash_log() 273 self.log_proc, self.hilog_proc = self.config.device.device_log_collector.\ 274 start_catch_device_log(hilog_file_pipe=hilog_file_pipe) 275 self._run_oh_jsunit(config_file, request) 276 except Exception as exception: 277 self.error_message = exception 278 if not getattr(exception, "error_no", ""): 279 setattr(exception, "error_no", "03409") 280 LOG.exception(self.error_message, exc_info=True, error_no="03409") 281 raise exception 282 finally: 283 serial = "{}_{}".format(str(self.config.device.__get_serial__()), time.time_ns()) 284 log_tar_file_name = "{}_{}".format(request.get_module_name(), 285 str(serial).replace(":", "_")) 286 if hasattr(self.config, "device_log") and \ 287 self.config.device_log == ConfigConst.device_log_on \ 288 and hasattr(self.config.device, "start_get_crash_log"): 289 self.config.device.device_log_collector.start_get_crash_log(log_tar_file_name) 290 self.config.device.device_log_collector.remove_log_address(self.device_log, self.hilog) 291 self.config.device.device_log_collector.stop_catch_device_log(self.log_proc) 292 self.config.device.device_log_collector.stop_catch_device_log(self.hilog_proc) 293 self.result = check_result_report( 294 request.config.report_path, self.result, self.error_message) 295 296 def __dry_run_execute__(self, request): 297 LOG.debug("Start dry run xdevice JSUnit Test") 298 self.config = request.config 299 self.config.device = request.config.environment.devices[0] 300 config_file = request.root.source.config_file 301 suite_file = request.root.source.source_file 302 303 if not suite_file: 304 raise ParamError( 305 "test source '%s' not exists" % 306 request.root.source.source_string, error_no="00110") 307 LOG.debug("Test case file path: %s" % suite_file) 308 self._dry_run_oh_jsunit(config_file, request) 309 310 def _dry_run_oh_jsunit(self, config_file, request): 311 try: 312 if not os.path.exists(config_file): 313 LOG.error("Error: Test cases don't exist %s." % config_file) 314 raise ParamError( 315 "Error: Test cases don't exist %s." % config_file, 316 error_no="00102") 317 json_config = JsonParser(config_file) 318 self.kits = get_kit_instances(json_config, 319 self.config.resource_path, 320 self.config.testcases_path) 321 322 self._get_driver_config(json_config) 323 self.config.device.connector_command("target mount") 324 do_module_kit_setup(request, self.kits) 325 self.runner = OHJSUnitTestRunner(self.config) 326 self.runner.suites_name = request.get_module_name() 327 # execute test case 328 self._get_runner_config(json_config) 329 oh_jsunit_para_parse(self.runner, self.config.testargs) 330 331 test_to_run = self._collect_test_to_run() 332 LOG.info("Collected suite count is: {}, test count is: {}". 333 format(len(self.runner.expect_tests_dict.keys()), 334 len(test_to_run) if test_to_run else 0)) 335 finally: 336 do_module_kit_teardown(request) 337 338 def _run_oh_jsunit(self, config_file, request): 339 try: 340 if not os.path.exists(config_file): 341 LOG.error("Error: Test cases don't exist %s." % config_file) 342 raise ParamError( 343 "Error: Test cases don't exist %s." % config_file, 344 error_no="00102") 345 json_config = JsonParser(config_file) 346 self.kits = get_kit_instances(json_config, 347 self.config.resource_path, 348 self.config.testcases_path) 349 350 self._get_driver_config(json_config) 351 self.config.device.connector_command("target mount") 352 do_module_kit_setup(request, self.kits) 353 self.runner = OHJSUnitTestRunner(self.config) 354 self.runner.suites_name = request.get_module_name() 355 self._get_runner_config(json_config) 356 if hasattr(self.config, "history_report_path") and \ 357 self.config.testargs.get("test"): 358 self._do_test_retry(request.listeners, self.config.testargs) 359 else: 360 if self.rerun: 361 self.runner.retry_times = self.runner.MAX_RETRY_TIMES 362 # execute test case 363 self._make_exclude_list_file(request) 364 oh_jsunit_para_parse(self.runner, self.config.testargs) 365 self._do_test_run(listener=request.listeners) 366 367 finally: 368 do_module_kit_teardown(request) 369 370 def _get_driver_config(self, json_config): 371 package = get_config_value('package-name', 372 json_config.get_driver(), False) 373 module = get_config_value('module-name', 374 json_config.get_driver(), False) 375 bundle = get_config_value('bundle-name', 376 json_config. get_driver(), False) 377 is_rerun = get_config_value('rerun', json_config.get_driver(), False) 378 379 self.config.package_name = package 380 self.config.module_name = module 381 self.config.bundle_name = bundle 382 self.rerun = True if is_rerun == 'true' else False 383 384 if not package and not module: 385 raise ParamError("Neither package nor module is found" 386 " in config file.", error_no="03201") 387 timeout_config = get_config_value("shell-timeout", 388 json_config.get_driver(), False) 389 if timeout_config: 390 self.config.timeout = int(timeout_config) 391 else: 392 self.config.timeout = TIME_OUT 393 394 def _get_runner_config(self, json_config): 395 test_timeout = get_config_value('test-timeout', 396 json_config.get_driver(), False) 397 if test_timeout: 398 self.runner.add_arg("wait_time", int(test_timeout)) 399 400 testcase_timeout = get_config_value('testcase-timeout', 401 json_config.get_driver(), False) 402 if testcase_timeout: 403 self.runner.add_arg("timeout", int(testcase_timeout)) 404 405 def _do_test_run(self, listener): 406 test_to_run = self._collect_test_to_run() 407 LOG.info("Collected suite count is: {}, test count is: {}". 408 format(len(self.runner.expect_tests_dict.keys()), 409 len(test_to_run) if test_to_run else 0)) 410 if not test_to_run or not self.rerun: 411 self.runner.run(listener) 412 self.runner.notify_finished() 413 else: 414 self._run_with_rerun(listener, test_to_run) 415 416 def _collect_test_to_run(self): 417 run_results = self.runner.dry_run() 418 return run_results 419 420 def _run_tests(self, listener): 421 test_tracker = CollectingPassListener() 422 listener_copy = listener.copy() 423 listener_copy.append(test_tracker) 424 self.runner.run(listener_copy) 425 test_run = test_tracker.get_current_run_results() 426 return test_run 427 428 def _run_with_rerun(self, listener, expected_tests): 429 LOG.debug("Ready to run with rerun, expect run: %s" 430 % len(expected_tests)) 431 test_run = self._run_tests(listener) 432 self.runner.retry_times -= 1 433 LOG.debug("Run with rerun, has run: %s" % len(test_run) 434 if test_run else 0) 435 if len(test_run) < len(expected_tests): 436 expected_tests = TestDescription.remove_test(expected_tests, 437 test_run) 438 if not expected_tests: 439 LOG.debug("No tests to re-run twice,please check") 440 self.runner.notify_finished() 441 else: 442 self._rerun_twice(expected_tests, listener) 443 else: 444 LOG.debug("Rerun once success") 445 self.runner.notify_finished() 446 447 def _rerun_twice(self, expected_tests, listener): 448 tests = [] 449 for test in expected_tests: 450 tests.append("%s#%s" % (test.class_name, test.test_name)) 451 self.runner.add_arg("class", ",".join(tests)) 452 LOG.debug("Ready to rerun twice, expect run: %s" % len(expected_tests)) 453 test_run = self._run_tests(listener) 454 self.runner.retry_times -= 1 455 LOG.debug("Rerun twice, has run: %s" % len(test_run)) 456 if len(test_run) < len(expected_tests): 457 expected_tests = TestDescription.remove_test(expected_tests, 458 test_run) 459 if not expected_tests: 460 LOG.debug("No tests to re-run third,please check") 461 self.runner.notify_finished() 462 else: 463 self._rerun_third(expected_tests, listener) 464 else: 465 LOG.debug("Rerun twice success") 466 self.runner.notify_finished() 467 468 def _rerun_third(self, expected_tests, listener): 469 tests = [] 470 for test in expected_tests: 471 tests.append("%s#%s" % (test.class_name, test.test_name)) 472 self.runner.add_arg("class", ",".join(tests)) 473 LOG.debug("Rerun to rerun third, expect run: %s" % len(expected_tests)) 474 self._run_tests(listener) 475 LOG.debug("Rerun third success") 476 self.runner.notify_finished() 477 478 def _make_exclude_list_file(self, request): 479 if "all-test-file-exclude-filter" in self.config.testargs: 480 json_file_list = self.config.testargs.get( 481 "all-test-file-exclude-filter") 482 self.config.testargs.pop("all-test-file-exclude-filter") 483 if not json_file_list: 484 LOG.warning("all-test-file-exclude-filter value is empty!") 485 else: 486 if not os.path.isfile(json_file_list[0]): 487 LOG.warning( 488 "[{}] is not a valid file".format(json_file_list[0])) 489 return 490 file_open = os.open(json_file_list[0], os.O_RDONLY, 491 stat.S_IWUSR | stat.S_IRUSR) 492 with os.fdopen(file_open, "r") as file_handler: 493 json_data = json.load(file_handler) 494 exclude_list = json_data.get( 495 DeviceTestType.oh_jsunit_test, []) 496 filter_list = [] 497 for exclude in exclude_list: 498 if request.get_module_name() not in exclude: 499 continue 500 filter_list.extend(exclude.get(request.get_module_name())) 501 if not isinstance(self.config.testargs, dict): 502 return 503 if 'notClass' in self.config.testargs.keys(): 504 filter_list.extend(self.config.testargs.get('notClass', [])) 505 self.config.testargs.update({"notClass": filter_list}) 506 507 def _do_test_retry(self, listener, testargs): 508 tests_dict = dict() 509 for test in testargs.get("test"): 510 test_item = test.split("#") 511 if len(test_item) != 2: 512 continue 513 if test_item[0] not in tests_dict: 514 tests_dict.update({test_item[0] : []}) 515 tests_dict.get(test_item[0]).append(test_item[1]) 516 self.runner.add_arg("class", test) 517 self.runner.expect_tests_dict = tests_dict 518 self.config.testargs.pop("test") 519 self.runner.run(listener) 520 self.runner.notify_finished() 521 522 def __result__(self): 523 return self.result if os.path.exists(self.result) else "" 524 525 526class OHJSUnitTestRunner: 527 MAX_RETRY_TIMES = 3 528 529 def __init__(self, config): 530 self.arg_list = {} 531 self.suites_name = None 532 self.config = config 533 self.rerun_attemp = 3 534 self.suite_recorder = {} 535 self.finished = False 536 self.expect_tests_dict = dict() 537 self.finished_observer = None 538 self.retry_times = 1 539 540 def dry_run(self): 541 parsers = get_plugin(Plugin.PARSER, CommonParserType.oh_jsunit_list) 542 if parsers: 543 parsers = parsers[:1] 544 parser_instances = [] 545 for parser in parsers: 546 parser_instance = parser.__class__() 547 parser_instances.append(parser_instance) 548 handler = ShellHandler(parser_instances) 549 command = self._get_dry_run_command() 550 self.config.device.execute_shell_command( 551 command, timeout=self.config.timeout, receiver=handler, retry=0) 552 self.expect_tests_dict = parser_instances[0].tests_dict 553 return parser_instances[0].tests 554 555 def run(self, listener): 556 handler = self._get_shell_handler(listener) 557 command = self._get_run_command() 558 self.config.device.execute_shell_command( 559 command, timeout=self.config.timeout, receiver=handler, retry=0) 560 561 def notify_finished(self): 562 if self.finished_observer: 563 self.finished_observer.notify_task_finished() 564 self.retry_times -= 1 565 566 def _get_shell_handler(self, listener): 567 parsers = get_plugin(Plugin.PARSER, CommonParserType.oh_jsunit) 568 if parsers: 569 parsers = parsers[:1] 570 parser_instances = [] 571 for parser in parsers: 572 parser_instance = parser.__class__() 573 parser_instance.suites_name = self.suites_name 574 parser_instance.listeners = listener 575 parser_instance.runner = self 576 parser_instances.append(parser_instance) 577 self.finished_observer = parser_instance 578 handler = ShellHandler(parser_instances) 579 return handler 580 581 def add_arg(self, name, value): 582 if not name or not value: 583 return 584 self.arg_list[name] = value 585 586 def remove_arg(self, name): 587 if not name: 588 return 589 if name in self.arg_list: 590 del self.arg_list[name] 591 592 def get_args_command(self): 593 args_commands = "" 594 for key, value in self.arg_list.items(): 595 if "wait_time" == key: 596 args_commands = "%s -w %s " % (args_commands, value) 597 else: 598 args_commands = "%s -s %s %s " % (args_commands, key, value) 599 return args_commands 600 601 def _get_run_command(self): 602 command = "" 603 if self.config.package_name: 604 # aa test -p ${packageName} -b ${bundleName}-s unittest OpenHarmonyTestRunner 605 command = "aa test -p %s -b %s -s unittest OpenHarmonyTestRunner" \ 606 " %s" % (self.config.package_name, 607 self.config.bundle_name, 608 self.get_args_command()) 609 elif self.config.module_name: 610 # aa test -m ${moduleName} -b ${bundleName} -s unittest OpenHarmonyTestRunner 611 command = "aa test -m %s -b %s -s unittest OpenHarmonyTestRunner" \ 612 " %s" % (self.config.module_name, 613 self.config.bundle_name, 614 self.get_args_command()) 615 return command 616 617 def _get_dry_run_command(self): 618 command = "" 619 if self.config.package_name: 620 command = "aa test -p %s -b %s -s unittest OpenHarmonyTestRunner" \ 621 " %s -s dryRun true" % (self.config.package_name, 622 self.config.bundle_name, 623 self.get_args_command()) 624 elif self.config.module_name: 625 command = "aa test -m %s -b %s -s unittest OpenHarmonyTestRunner" \ 626 " %s -s dryRun true" % (self.config.module_name, 627 self.config.bundle_name, 628 self.get_args_command()) 629 630 return command 631