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 try: 284 self._handle_logs(request) 285 finally: 286 self.result = check_result_report( 287 request.config.report_path, self.result, self.error_message) 288 289 def __dry_run_execute__(self, request): 290 LOG.debug("Start dry run xdevice JSUnit Test") 291 self.config = request.config 292 self.config.device = request.config.environment.devices[0] 293 config_file = request.root.source.config_file 294 suite_file = request.root.source.source_file 295 296 if not suite_file: 297 raise ParamError( 298 "test source '%s' not exists" % 299 request.root.source.source_string, error_no="00110") 300 LOG.debug("Test case file path: %s" % suite_file) 301 self._dry_run_oh_jsunit(config_file, request) 302 303 def _dry_run_oh_jsunit(self, config_file, request): 304 try: 305 if not os.path.exists(config_file): 306 LOG.error("Error: Test cases don't exist %s." % config_file) 307 raise ParamError( 308 "Error: Test cases don't exist %s." % config_file, 309 error_no="00102") 310 json_config = JsonParser(config_file) 311 self.kits = get_kit_instances(json_config, 312 self.config.resource_path, 313 self.config.testcases_path) 314 315 self._get_driver_config(json_config) 316 self.config.device.connector_command("target mount") 317 do_module_kit_setup(request, self.kits) 318 self.runner = OHJSUnitTestRunner(self.config) 319 self.runner.suites_name = request.get_module_name() 320 # execute test case 321 self._get_runner_config(json_config) 322 oh_jsunit_para_parse(self.runner, self.config.testargs) 323 324 test_to_run = self._collect_test_to_run() 325 LOG.info("Collected suite count is: {}, test count is: {}". 326 format(len(self.runner.expect_tests_dict.keys()), 327 len(test_to_run) if test_to_run else 0)) 328 finally: 329 do_module_kit_teardown(request) 330 331 def _run_oh_jsunit(self, config_file, request): 332 try: 333 if not os.path.exists(config_file): 334 LOG.error("Error: Test cases don't exist %s." % config_file) 335 raise ParamError( 336 "Error: Test cases don't exist %s." % config_file, 337 error_no="00102") 338 json_config = JsonParser(config_file) 339 self.kits = get_kit_instances(json_config, 340 self.config.resource_path, 341 self.config.testcases_path) 342 343 self._get_driver_config(json_config) 344 self.config.device.connector_command("target mount") 345 do_module_kit_setup(request, self.kits) 346 self.runner = OHJSUnitTestRunner(self.config) 347 self.runner.suites_name = request.get_module_name() 348 self._get_runner_config(json_config) 349 if hasattr(self.config, "history_report_path") and \ 350 self.config.testargs.get("test"): 351 self._do_test_retry(request.listeners, self.config.testargs) 352 else: 353 if self.rerun: 354 self.runner.retry_times = self.runner.MAX_RETRY_TIMES 355 # execute test case 356 self._make_exclude_list_file(request) 357 oh_jsunit_para_parse(self.runner, self.config.testargs) 358 self._do_test_run(listener=request.listeners) 359 360 finally: 361 do_module_kit_teardown(request) 362 363 def _get_driver_config(self, json_config): 364 package = get_config_value('package-name', 365 json_config.get_driver(), False) 366 module = get_config_value('module-name', 367 json_config.get_driver(), False) 368 bundle = get_config_value('bundle-name', 369 json_config. get_driver(), False) 370 is_rerun = get_config_value('rerun', json_config.get_driver(), False) 371 372 self.config.package_name = package 373 self.config.module_name = module 374 self.config.bundle_name = bundle 375 self.rerun = True if is_rerun == 'true' else False 376 377 if not package and not module: 378 raise ParamError("Neither package nor module is found" 379 " in config file.", error_no="03201") 380 timeout_config = get_config_value("shell-timeout", 381 json_config.get_driver(), False) 382 if timeout_config: 383 self.config.timeout = int(timeout_config) 384 else: 385 self.config.timeout = TIME_OUT 386 387 def _get_runner_config(self, json_config): 388 test_timeout = get_config_value('test-timeout', 389 json_config.get_driver(), False) 390 if test_timeout: 391 self.runner.add_arg("wait_time", int(test_timeout)) 392 393 testcase_timeout = get_config_value('testcase-timeout', 394 json_config.get_driver(), False) 395 if testcase_timeout: 396 self.runner.add_arg("timeout", int(testcase_timeout)) 397 398 def _do_test_run(self, listener): 399 test_to_run = self._collect_test_to_run() 400 LOG.info("Collected suite count is: {}, test count is: {}". 401 format(len(self.runner.expect_tests_dict.keys()), 402 len(test_to_run) if test_to_run else 0)) 403 if not test_to_run or not self.rerun: 404 self.runner.run(listener) 405 self.runner.notify_finished() 406 else: 407 self._run_with_rerun(listener, test_to_run) 408 409 def _collect_test_to_run(self): 410 run_results = self.runner.dry_run() 411 return run_results 412 413 def _run_tests(self, listener): 414 test_tracker = CollectingPassListener() 415 listener_copy = listener.copy() 416 listener_copy.append(test_tracker) 417 self.runner.run(listener_copy) 418 test_run = test_tracker.get_current_run_results() 419 return test_run 420 421 def _run_with_rerun(self, listener, expected_tests): 422 LOG.debug("Ready to run with rerun, expect run: %s" 423 % len(expected_tests)) 424 test_run = self._run_tests(listener) 425 self.runner.retry_times -= 1 426 LOG.debug("Run with rerun, has run: %s" % len(test_run) 427 if test_run else 0) 428 if len(test_run) < len(expected_tests): 429 expected_tests = TestDescription.remove_test(expected_tests, 430 test_run) 431 if not expected_tests: 432 LOG.debug("No tests to re-run twice,please check") 433 self.runner.notify_finished() 434 else: 435 self._rerun_twice(expected_tests, listener) 436 else: 437 LOG.debug("Rerun once success") 438 self.runner.notify_finished() 439 440 def _rerun_twice(self, expected_tests, listener): 441 tests = [] 442 for test in expected_tests: 443 tests.append("%s#%s" % (test.class_name, test.test_name)) 444 self.runner.add_arg("class", ",".join(tests)) 445 LOG.debug("Ready to rerun twice, expect run: %s" % len(expected_tests)) 446 test_run = self._run_tests(listener) 447 self.runner.retry_times -= 1 448 LOG.debug("Rerun twice, has run: %s" % len(test_run)) 449 if len(test_run) < len(expected_tests): 450 expected_tests = TestDescription.remove_test(expected_tests, 451 test_run) 452 if not expected_tests: 453 LOG.debug("No tests to re-run third,please check") 454 self.runner.notify_finished() 455 else: 456 self._rerun_third(expected_tests, listener) 457 else: 458 LOG.debug("Rerun twice success") 459 self.runner.notify_finished() 460 461 def _rerun_third(self, expected_tests, listener): 462 tests = [] 463 for test in expected_tests: 464 tests.append("%s#%s" % (test.class_name, test.test_name)) 465 self.runner.add_arg("class", ",".join(tests)) 466 LOG.debug("Rerun to rerun third, expect run: %s" % len(expected_tests)) 467 self._run_tests(listener) 468 LOG.debug("Rerun third success") 469 self.runner.notify_finished() 470 471 def _make_exclude_list_file(self, request): 472 if "all-test-file-exclude-filter" in self.config.testargs: 473 json_file_list = self.config.testargs.get( 474 "all-test-file-exclude-filter") 475 self.config.testargs.pop("all-test-file-exclude-filter") 476 if not json_file_list: 477 LOG.warning("all-test-file-exclude-filter value is empty!") 478 else: 479 if not os.path.isfile(json_file_list[0]): 480 LOG.warning( 481 "[{}] is not a valid file".format(json_file_list[0])) 482 return 483 file_open = os.open(json_file_list[0], os.O_RDONLY, 484 stat.S_IWUSR | stat.S_IRUSR) 485 with os.fdopen(file_open, "r") as file_handler: 486 json_data = json.load(file_handler) 487 exclude_list = json_data.get( 488 DeviceTestType.oh_jsunit_test, []) 489 filter_list = [] 490 for exclude in exclude_list: 491 if request.get_module_name() not in exclude: 492 continue 493 filter_list.extend(exclude.get(request.get_module_name())) 494 if not isinstance(self.config.testargs, dict): 495 return 496 if 'notClass' in self.config.testargs.keys(): 497 filter_list.extend(self.config.testargs.get('notClass', [])) 498 self.config.testargs.update({"notClass": filter_list}) 499 500 def _do_test_retry(self, listener, testargs): 501 tests_dict = dict() 502 case_list = list() 503 for test in testargs.get("test"): 504 test_item = test.split("#") 505 if len(test_item) != 2: 506 continue 507 case_list.append(test) 508 if test_item[0] not in tests_dict: 509 tests_dict.update({test_item[0] : []}) 510 tests_dict.get(test_item[0]).append( 511 TestDescription(test_item[0], test_item[1])) 512 self.runner.add_arg("class", ",".join(case_list)) 513 self.runner.expect_tests_dict = tests_dict 514 self.config.testargs.pop("test") 515 self.runner.run(listener) 516 self.runner.notify_finished() 517 518 def _handle_logs(self, request): 519 serial = "{}_{}".format(str(self.config.device.__get_serial__()), time.time_ns()) 520 log_tar_file_name = "{}_{}".format(request.get_module_name(), 521 str(serial).replace(":", "_")) 522 if hasattr(self.config, "device_log") and \ 523 self.config.device_log == ConfigConst.device_log_on \ 524 and hasattr(self.config.device, "start_get_crash_log"): 525 self.config.device.device_log_collector. \ 526 start_get_crash_log(log_tar_file_name) 527 self.config.device.device_log_collector. \ 528 remove_log_address(self.device_log, self.hilog) 529 self.config.device.device_log_collector. \ 530 stop_catch_device_log(self.log_proc) 531 self.config.device.device_log_collector. \ 532 stop_catch_device_log(self.hilog_proc) 533 534 def __result__(self): 535 return self.result if os.path.exists(self.result) else "" 536 537 538class OHJSUnitTestRunner: 539 MAX_RETRY_TIMES = 3 540 541 def __init__(self, config): 542 self.arg_list = {} 543 self.suites_name = None 544 self.config = config 545 self.rerun_attemp = 3 546 self.suite_recorder = {} 547 self.finished = False 548 self.expect_tests_dict = dict() 549 self.finished_observer = None 550 self.retry_times = 1 551 552 def dry_run(self): 553 parsers = get_plugin(Plugin.PARSER, CommonParserType.oh_jsunit_list) 554 if parsers: 555 parsers = parsers[:1] 556 parser_instances = [] 557 for parser in parsers: 558 parser_instance = parser.__class__() 559 parser_instances.append(parser_instance) 560 handler = ShellHandler(parser_instances) 561 command = self._get_dry_run_command() 562 self.config.device.execute_shell_command( 563 command, timeout=self.config.timeout, receiver=handler, retry=0) 564 self.expect_tests_dict = parser_instances[0].tests_dict 565 return parser_instances[0].tests 566 567 def run(self, listener): 568 handler = self._get_shell_handler(listener) 569 command = self._get_run_command() 570 self.config.device.execute_shell_command( 571 command, timeout=self.config.timeout, receiver=handler, retry=0) 572 573 def notify_finished(self): 574 if self.finished_observer: 575 self.finished_observer.notify_task_finished() 576 self.retry_times -= 1 577 578 def _get_shell_handler(self, listener): 579 parsers = get_plugin(Plugin.PARSER, CommonParserType.oh_jsunit) 580 if parsers: 581 parsers = parsers[:1] 582 parser_instances = [] 583 for parser in parsers: 584 parser_instance = parser.__class__() 585 parser_instance.suites_name = self.suites_name 586 parser_instance.listeners = listener 587 parser_instance.runner = self 588 parser_instances.append(parser_instance) 589 self.finished_observer = parser_instance 590 handler = ShellHandler(parser_instances) 591 return handler 592 593 def add_arg(self, name, value): 594 if not name or not value: 595 return 596 self.arg_list[name] = value 597 598 def remove_arg(self, name): 599 if not name: 600 return 601 if name in self.arg_list: 602 del self.arg_list[name] 603 604 def get_args_command(self): 605 args_commands = "" 606 for key, value in self.arg_list.items(): 607 if "wait_time" == key: 608 args_commands = "%s -w %s " % (args_commands, value) 609 else: 610 args_commands = "%s -s %s %s " % (args_commands, key, value) 611 return args_commands 612 613 def _get_run_command(self): 614 command = "" 615 if self.config.package_name: 616 # aa test -p ${packageName} -b ${bundleName}-s unittest OpenHarmonyTestRunner 617 command = "aa test -p %s -b %s -s unittest OpenHarmonyTestRunner" \ 618 " %s" % (self.config.package_name, 619 self.config.bundle_name, 620 self.get_args_command()) 621 elif self.config.module_name: 622 # aa test -m ${moduleName} -b ${bundleName} -s unittest OpenHarmonyTestRunner 623 command = "aa test -m %s -b %s -s unittest OpenHarmonyTestRunner" \ 624 " %s" % (self.config.module_name, 625 self.config.bundle_name, 626 self.get_args_command()) 627 return command 628 629 def _get_dry_run_command(self): 630 command = "" 631 if self.config.package_name: 632 command = "aa test -p %s -b %s -s unittest OpenHarmonyTestRunner" \ 633 " %s -s dryRun true" % (self.config.package_name, 634 self.config.bundle_name, 635 self.get_args_command()) 636 elif self.config.module_name: 637 command = "aa test -m %s -b %s -s unittest OpenHarmonyTestRunner" \ 638 " %s -s dryRun true" % (self.config.module_name, 639 self.config.bundle_name, 640 self.get_args_command()) 641 642 return command 643