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 time 22import json 23import stat 24 25from xdevice import ConfigConst 26from xdevice import ParamError 27from xdevice import IDriver 28from xdevice import platform_logger 29from xdevice import Plugin 30from xdevice import get_plugin 31from xdevice import JsonParser 32from xdevice import ShellHandler 33from xdevice import driver_output_method 34from xdevice import TestDescription 35from xdevice import get_device_log_file 36from xdevice import check_result_report 37from xdevice import get_kit_instances 38from xdevice import get_config_value 39from xdevice import do_module_kit_setup 40from xdevice import do_module_kit_teardown 41from xdevice import DeviceTestType 42from xdevice import CommonParserType 43from xdevice import FilePermission 44from xdevice import CollectingTestListener 45from xdevice import ShellCommandUnresponsiveException 46from xdevice import gtest_para_parse 47 48from ohos.environment.dmlib import process_command_ret 49from ohos.parser.parser import _ACE_LOG_MARKER 50 51__all__ = ["CppTestDriver", "DexTestDriver", "HapTestDriver", 52 "JSUnitTestDriver", "JUnitTestDriver", "RemoteTestRunner", 53 "RemoteDexRunner"] 54LOG = platform_logger("Drivers") 55DEFAULT_TEST_PATH = "/%s/%s/" % ("data", "test") 56ON_DEVICE_TEST_DIR_LOCATION = "/%s/%s/%s/" % ("data", "local", "tmp") 57 58FAILED_RUN_TEST_ATTEMPTS = 3 59TIME_OUT = 900 * 1000 60 61 62def get_xml_output(config, json_config): 63 xml_output = config.testargs.get("xml-output") 64 if not xml_output: 65 if get_config_value('xml-output', json_config.get_driver(), False): 66 xml_output = get_config_value('xml-output', 67 json_config.get_driver(), False) 68 else: 69 xml_output = "false" 70 else: 71 xml_output = xml_output[0] 72 xml_output = str(xml_output).lower() 73 return xml_output 74 75 76def get_result_savepath(testsuit_path, result_rootpath): 77 findkey = "%stests%s" % (os.sep, os.sep) 78 filedir, _ = os.path.split(testsuit_path) 79 pos = filedir.find(findkey) 80 if -1 != pos: 81 subpath = filedir[pos + len(findkey):] 82 pos1 = subpath.find(os.sep) 83 if -1 != pos1: 84 subpath = subpath[pos1 + len(os.sep):] 85 result_path = os.path.join(result_rootpath, "result", subpath) 86 else: 87 result_path = os.path.join(result_rootpath, "result") 88 else: 89 result_path = os.path.join(result_rootpath, "result") 90 91 if not os.path.exists(result_path): 92 os.makedirs(result_path) 93 94 LOG.info("Result save path = %s" % result_path) 95 return result_path 96 97 98# all testsuit common Unavailable test result xml 99def _create_empty_result_file(filepath, filename, error_message): 100 error_message = str(error_message) 101 error_message = error_message.replace("\"", """) 102 error_message = error_message.replace("<", "<") 103 error_message = error_message.replace(">", ">") 104 error_message = error_message.replace("&", "&") 105 if filename.endswith(".hap"): 106 filename = filename.split(".")[0] 107 if not os.path.exists(filepath): 108 file_open = os.open(filepath, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 109 FilePermission.mode_755) 110 with os.fdopen(file_open, "w") as file_desc: 111 time_stamp = time.strftime("%Y-%m-%d %H:%M:%S", 112 time.localtime()) 113 file_desc.write('<?xml version="1.0" encoding="UTF-8"?>\n') 114 file_desc.write('<testsuites tests="0" failures="0" ' 115 'disabled="0" errors="0" timestamp="%s" ' 116 'time="0" name="AllTests">\n' % time_stamp) 117 file_desc.write( 118 ' <testsuite name="%s" tests="0" failures="0" ' 119 'disabled="0" errors="0" time="0.0" ' 120 'unavailable="1" message="%s">\n' % 121 (filename, error_message)) 122 file_desc.write(' </testsuite>\n') 123 file_desc.write('</testsuites>\n') 124 file_desc.flush() 125 return 126 127 128class ResultManager(object): 129 def __init__(self, testsuit_path, result_rootpath, device, 130 device_testpath): 131 self.testsuite_path = testsuit_path 132 self.result_rootpath = result_rootpath 133 self.device = device 134 self.device_testpath = device_testpath 135 self.testsuite_name = os.path.basename(self.testsuite_path) 136 self.is_coverage = False 137 138 def set_is_coverage(self, is_coverage): 139 self.is_coverage = is_coverage 140 141 def get_test_results(self, error_message=""): 142 # Get test result files 143 filepath = self.obtain_test_result_file() 144 if not os.path.exists(filepath): 145 _create_empty_result_file(filepath, self.testsuite_name, 146 error_message) 147 148 # Get coverage data files 149 if self.is_coverage: 150 self.obtain_coverage_data() 151 152 return filepath 153 154 def obtain_test_result_file(self): 155 result_savepath = get_result_savepath(self.testsuite_path, 156 self.result_rootpath) 157 if self.testsuite_path.endswith('.hap'): 158 filepath = os.path.join(result_savepath, "%s.xml" % str( 159 self.testsuite_name).split(".")[0]) 160 161 remote_result_name = "" 162 if self.device.is_file_exist(os.path.join(self.device_testpath, 163 "testcase_result.xml")): 164 remote_result_name = "testcase_result.xml" 165 elif self.device.is_file_exist(os.path.join(self.device_testpath, 166 "report.xml")): 167 remote_result_name = "report.xml" 168 169 if remote_result_name: 170 self.device.pull_file( 171 os.path.join(self.device_testpath, remote_result_name), 172 filepath) 173 else: 174 LOG.error("%s no report file", self.device_testpath) 175 176 else: 177 filepath = os.path.join(result_savepath, "%s.xml" % 178 self.testsuite_name) 179 remote_result_file = os.path.join(self.device_testpath, 180 "%s.xml" % self.testsuite_name) 181 182 if self.device.is_file_exist(remote_result_file): 183 self.device.pull_file(remote_result_file, result_savepath) 184 else: 185 LOG.error("%s not exists", remote_result_file) 186 return filepath 187 188 def is_exist_target_in_device(self, path, target): 189 command = "ls -l %s | grep %s" % (path, target) 190 191 check_result = False 192 stdout_info = self.device.execute_shell_command(command) 193 if stdout_info != "" and stdout_info.find(target) != -1: 194 check_result = True 195 return check_result 196 197 def obtain_coverage_data(self): 198 java_cov_path = os.path.abspath( 199 os.path.join(self.result_rootpath, "..", "coverage/data/exec")) 200 dst_target_name = "%s.exec" % self.testsuite_name 201 src_target_name = "jacoco.exec" 202 if self.is_exist_target_in_device(self.device_testpath, 203 src_target_name): 204 if not os.path.exists(java_cov_path): 205 os.makedirs(java_cov_path) 206 self.device.pull_file( 207 os.path.join(self.device_testpath, src_target_name), 208 os.path.join(java_cov_path, dst_target_name)) 209 210 cxx_cov_path = os.path.abspath( 211 os.path.join(self.result_rootpath, "..", "coverage/data/cxx", 212 self.testsuite_name)) 213 target_name = "obj" 214 if self.is_exist_target_in_device(self.device_testpath, target_name): 215 if not os.path.exists(cxx_cov_path): 216 os.makedirs(cxx_cov_path) 217 src_file = os.path.join(self.device_testpath, target_name) 218 self.device.pull_file(src_file, cxx_cov_path) 219 220 221@Plugin(type=Plugin.DRIVER, id=DeviceTestType.cpp_test) 222class CppTestDriver(IDriver): 223 """ 224 CppTestDriver is a Test that runs a native test package on given harmony 225 device. 226 """ 227 228 def __init__(self): 229 self.result = "" 230 self.error_message = "" 231 self.config = None 232 self.rerun = True 233 self.rerun_all = True 234 self.runner = None 235 # log 236 self.device_log = None 237 self.hilog = None 238 self.log_proc = None 239 self.hilog_proc = None 240 241 def __check_environment__(self, device_options): 242 pass 243 244 def __check_config__(self, config): 245 pass 246 247 def __execute__(self, request): 248 try: 249 LOG.debug("Start execute xdevice extension CppTest") 250 251 self.config = request.config 252 self.config.device = request.config.environment.devices[0] 253 254 config_file = request.root.source.config_file 255 self.result = "%s.xml" % \ 256 os.path.join(request.config.report_path, 257 "result", request.root.source.test_name) 258 259 self.device_log = get_device_log_file( 260 request.config.report_path, 261 request.config.device.__get_serial__(), 262 "device_log", 263 module_name=request.get_module_name()) 264 265 self.hilog = get_device_log_file( 266 request.config.report_path, 267 request.config.device.__get_serial__(), 268 "device_hilog", 269 module_name=request.get_module_name()) 270 271 device_log_open = os.open(self.device_log, os.O_WRONLY | os.O_CREAT | 272 os.O_APPEND, FilePermission.mode_755) 273 hilog_open = os.open(self.hilog, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 274 FilePermission.mode_755) 275 self.config.device.device_log_collector.add_log_address(self.device_log, self.hilog) 276 with os.fdopen(device_log_open, "a") as log_file_pipe, \ 277 os.fdopen(hilog_open, "a") as hilog_file_pipe: 278 self.log_proc, self.hilog_proc = self.config.device.device_log_collector.\ 279 start_catch_device_log(log_file_pipe, hilog_file_pipe) 280 self._run_cpp_test(config_file, listeners=request.listeners, 281 request=request) 282 log_file_pipe.flush() 283 hilog_file_pipe.flush() 284 285 except Exception as exception: 286 self.error_message = exception 287 if not getattr(exception, "error_no", ""): 288 setattr(exception, "error_no", "03404") 289 LOG.exception(self.error_message, exc_info=False, error_no="03404") 290 raise exception 291 292 finally: 293 self.config.device.device_log_collector.remove_log_address(self.device_log, self.hilog) 294 self.config.device.device_log_collector.stop_catch_device_log(self.log_proc) 295 self.config.device.device_log_collector.stop_catch_device_log(self.hilog_proc) 296 self.result = check_result_report( 297 request.config.report_path, self.result, self.error_message) 298 299 def _run_cpp_test(self, config_file, listeners=None, request=None): 300 try: 301 if not os.path.exists(config_file): 302 LOG.error("Error: Test cases don't exit %s." % config_file, 303 error_no="00102") 304 raise ParamError( 305 "Error: Test cases don't exit %s." % config_file, 306 error_no="00102") 307 308 json_config = JsonParser(config_file) 309 kits = get_kit_instances(json_config, self.config.resource_path, 310 self.config.testcases_path) 311 312 for listener in listeners: 313 listener.device_sn = self.config.device.device_sn 314 315 self._get_driver_config(json_config) 316 do_module_kit_setup(request, kits) 317 self.runner = RemoteCppTestRunner(self.config) 318 self.runner.suite_name = request.root.source.test_name 319 320 if hasattr(self.config, "history_report_path") and \ 321 self.config.testargs.get("test"): 322 self._do_test_retry(listeners, self.config.testargs) 323 else: 324 gtest_para_parse(self.config.testargs, self.runner, request) 325 self._do_test_run(listeners) 326 327 finally: 328 do_module_kit_teardown(request) 329 330 def _do_test_retry(self, listener, testargs): 331 for test in testargs.get("test"): 332 test_item = test.split("#") 333 if len(test_item) != 2: 334 continue 335 self.runner.add_instrumentation_arg( 336 "gtest_filter", "%s.%s" % (test_item[0], test_item[1])) 337 self.runner.run(listener) 338 339 def _do_test_run(self, listener): 340 test_to_run = self._collect_test_to_run() 341 LOG.info("Collected test count is: %s" % (len(test_to_run) 342 if test_to_run else 0)) 343 if not test_to_run: 344 self.runner.run(listener) 345 else: 346 self._run_with_rerun(listener, test_to_run) 347 348 def _collect_test_to_run(self): 349 if self.rerun: 350 self.runner.add_instrumentation_arg("gtest_list_tests", True) 351 self.runner.add_instrumentation_arg("gtest_color", "no") 352 run_results = self.runner.dry_run() 353 self.runner.remove_instrumentation_arg("gtest_list_tests") 354 return run_results 355 return None 356 357 def _run_tests(self, listener): 358 test_tracker = CollectingTestListener() 359 listener_copy = listener.copy() 360 listener_copy.append(test_tracker) 361 self.runner.run(listener_copy) 362 test_run = test_tracker.get_current_run_results() 363 return test_run 364 365 def _run_with_rerun(self, listener, expected_tests): 366 LOG.debug("Ready to run with rerun, expect run: %s" 367 % len(expected_tests)) 368 test_run = self._run_tests(listener) 369 LOG.debug("Run with rerun, has run: %s" % len(test_run) 370 if test_run else 0) 371 if len(test_run) < len(expected_tests): 372 expected_tests = TestDescription.remove_test(expected_tests, 373 test_run) 374 if not expected_tests: 375 LOG.debug("No tests to re-run, all tests executed at least " 376 "once.") 377 if self.rerun_all: 378 self._rerun_all(expected_tests, listener) 379 else: 380 self._rerun_serially(expected_tests, listener) 381 382 def _rerun_all(self, expected_tests, listener): 383 tests = [] 384 for test in expected_tests: 385 tests.append("%s.%s" % (test.class_name, test.test_name)) 386 self.runner.add_instrumentation_arg("gtest_filter", ":".join(tests)) 387 LOG.debug("Ready to rerun file, expect run: %s" % len(expected_tests)) 388 test_run = self._run_tests(listener) 389 LOG.debug("Rerun file, has run: %s" % len(test_run)) 390 if len(test_run) < len(expected_tests): 391 expected_tests = TestDescription.remove_test(expected_tests, 392 test_run) 393 if not expected_tests: 394 LOG.debug("Rerun textFile success") 395 self._rerun_serially(expected_tests, listener) 396 397 def _rerun_serially(self, expected_tests, listener): 398 LOG.debug("Rerun serially, expected run: %s" % len(expected_tests)) 399 for test in expected_tests: 400 self.runner.add_instrumentation_arg( 401 "gtest_filter", "%s.%s" % (test.class_name, test.test_name)) 402 self.runner.rerun(listener, test) 403 self.runner.remove_instrumentation_arg("gtest_filter") 404 405 def _get_driver_config(self, json_config): 406 target_test_path = get_config_value('native-test-device-path', 407 json_config.get_driver(), False) 408 if target_test_path: 409 self.config.target_test_path = target_test_path 410 else: 411 self.config.target_test_path = DEFAULT_TEST_PATH 412 413 self.config.module_name = get_config_value( 414 'module-name', json_config.get_driver(), False) 415 416 timeout_config = get_config_value('native-test-timeout', 417 json_config.get_driver(), False) 418 if timeout_config: 419 self.config.timeout = int(timeout_config) 420 else: 421 self.config.timeout = TIME_OUT 422 423 rerun = get_config_value('rerun', json_config.get_driver(), False) 424 if isinstance(rerun, bool): 425 self.rerun = rerun 426 elif str(rerun).lower() == "false": 427 self.rerun = False 428 else: 429 self.rerun = True 430 431 def __result__(self): 432 return self.result if os.path.exists(self.result) else "" 433 434 435class RemoteCppTestRunner: 436 def __init__(self, config): 437 self.arg_list = {} 438 self.suite_name = None 439 self.config = config 440 self.rerun_attempt = FAILED_RUN_TEST_ATTEMPTS 441 442 def dry_run(self): 443 parsers = get_plugin(Plugin.PARSER, CommonParserType.cpptest_list) 444 if parsers: 445 parsers = parsers[:1] 446 parser_instances = [] 447 for parser in parsers: 448 parser_instance = parser.__class__() 449 parser_instances.append(parser_instance) 450 handler = ShellHandler(parser_instances) 451 handler.add_process_method(driver_output_method) 452 453 command = "cd %s; chmod +x *; ./%s %s" \ 454 % (self.config.target_test_path, self.config.module_name, 455 self.get_args_command()) 456 457 self.config.device.execute_shell_command( 458 command, timeout=self.config.timeout, receiver=handler, retry=0) 459 return parser_instances[0].tests 460 461 def run(self, listener): 462 handler = self._get_shell_handler(listener) 463 bin_path = "{}/{}".format(self.config.target_test_path, 464 self.config.module_name) 465 command = "cd %s; chmod +x *; hilog -d %s; ./%s %s" \ 466 % (self.config.target_test_path, 467 bin_path, 468 self.config.module_name, 469 self.get_args_command()) 470 471 self.config.device.execute_shell_command( 472 command, timeout=self.config.timeout, receiver=handler, retry=0) 473 474 def rerun(self, listener, test): 475 if self.rerun_attempt: 476 test_tracker = CollectingTestListener() 477 listener_copy = listener.copy() 478 listener_copy.append(test_tracker) 479 handler = self._get_shell_handler(listener_copy) 480 try: 481 bin_path = "{}/{}".format(self.config.target_test_path, 482 self.config.module_name) 483 command = "cd %s; chmod +x *; hilog -d %s; ./%s %s" \ 484 % (self.config.target_test_path, 485 bin_path, 486 self.config.module_name, 487 self.get_args_command()) 488 489 self.config.device.execute_shell_command( 490 command, timeout=self.config.timeout, receiver=handler, 491 retry=0) 492 493 except ShellCommandUnresponsiveException as _: 494 LOG.debug("Exception: ShellCommandUnresponsiveException") 495 finally: 496 if not len(test_tracker.get_current_run_results()): 497 LOG.debug("No test case is obtained finally") 498 self.rerun_attempt -= 1 499 handler.parsers[0].mark_test_as_blocked(test) 500 else: 501 LOG.debug("Not execute and mark as blocked finally") 502 handler = self._get_shell_handler(listener) 503 handler.parsers[0].mark_test_as_blocked(test) 504 505 def add_instrumentation_arg(self, name, value): 506 if not name or not value: 507 return 508 self.arg_list[name] = value 509 510 def remove_instrumentation_arg(self, name): 511 if not name: 512 return 513 if name in self.arg_list: 514 del self.arg_list[name] 515 516 def get_args_command(self): 517 args_commands = "" 518 for key, value in self.arg_list.items(): 519 if key == "gtest_list_tests": 520 args_commands = "%s --%s" % (args_commands, key) 521 else: 522 args_commands = "%s --%s=%s" % (args_commands, key, value) 523 return args_commands 524 525 def _get_shell_handler(self, listener): 526 parsers = get_plugin(Plugin.PARSER, CommonParserType.cpptest) 527 if parsers: 528 parsers = parsers[:1] 529 parser_instances = [] 530 for parser in parsers: 531 parser_instance = parser.__class__() 532 parser_instance.suite_name = self.suite_name 533 parser_instance.listeners = listener 534 parser_instances.append(parser_instance) 535 handler = ShellHandler(parser_instances) 536 handler.add_process_method(driver_output_method) 537 return handler 538 539 540@Plugin(type=Plugin.DRIVER, id=DeviceTestType.jsunit_test) 541class JSUnitTestDriver(IDriver): 542 """ 543 JSUnitTestDriver is a Test that runs a native test package on given device. 544 """ 545 546 def __init__(self): 547 self.xml_output = "false" 548 self.timeout = 30 * 1000 549 self.start_time = None 550 self.result = "" 551 self.error_message = "" 552 self.kits = [] 553 self.config = None 554 # log 555 self.device_log = None 556 self.hilog = None 557 self.log_proc = None 558 self.hilog_proc = None 559 self.ace_log_marker = "" 560 561 def __check_environment__(self, device_options): 562 pass 563 564 def __check_config__(self, config): 565 pass 566 567 def __execute__(self, request): 568 569 device = request.config.environment.devices[0] 570 exe_out = device.execute_shell_command( 571 "param get const.product.software.version") 572 LOG.debug("Software version is {}".format(exe_out)) 573 self.run_js_outer(request) 574 575 def generate_console_output(self, request, timeout): 576 LOG.info("prepare to read device log, may wait some time") 577 message_list = list() 578 label_list, suite_info, is_suites_end = self.read_device_log_timeout( 579 self.hilog, message_list, timeout) 580 if not is_suites_end and self.ace_log_marker: 581 message_list.append(self.ace_log_marker + " [end] run suites end\n") 582 LOG.warning("there is no suites end") 583 if len(label_list[0]) > 0 and sum(label_list[0]) != 0: 584 # the problem happened! when the sum of label list is not zero 585 self._insert_suite_end(label_list, message_list) 586 587 result_message = "".join(message_list) 588 message_list.clear() 589 expect_tests_dict = self._parse_suite_info(suite_info) 590 self._analyse_tests(request, result_message, expect_tests_dict) 591 592 def _insert_suite_end(self, label_list, message_list): 593 for i in range(len(label_list[0])): 594 if label_list[0][i] != 1: # skipp 595 continue 596 # check the start label, then peek next position 597 if i + 1 == len(label_list[0]): # next position at the tail 598 message_list.insert(-1, self.ace_log_marker + " [suite end]\n") 599 LOG.warning("there is no suite end") 600 continue 601 if label_list[0][i + 1] != 1: # 0 present the end label 602 continue 603 message_list.insert(label_list[1][i + 1], 604 self.ace_log_marker + " [suite end]\n") 605 LOG.warning("there is no suite end") 606 for j in range(i + 1, len(label_list[1])): 607 label_list[1][j] += 1 # move the index to next 608 609 def _analyse_tests(self, request, result_message, expect_tests_dict): 610 exclude_list = self._make_exclude_list_file(request) 611 exclude_list.extend(self._get_retry_skip_list(expect_tests_dict)) 612 listener_copy = request.listeners.copy() 613 parsers = get_plugin( 614 Plugin.PARSER, CommonParserType.jsunit) 615 if parsers: 616 parsers = parsers[:1] 617 for listener in listener_copy: 618 listener.device_sn = self.config.device.device_sn 619 parser_instances = [] 620 for parser in parsers: 621 parser_instance = parser.__class__() 622 parser_instance.suites_name = request.get_module_name() 623 parser_instance.listeners = listener_copy 624 parser_instances.append(parser_instance) 625 handler = ShellHandler(parser_instances) 626 handler.parsers[0].expect_tests_dict = expect_tests_dict 627 handler.parsers[0].exclude_list = exclude_list 628 process_command_ret(result_message, handler) 629 630 def _get_retry_skip_list(self, expect_tests_dict): 631 # get already pass case 632 skip_list = [] 633 if hasattr(self.config, "history_report_path") and \ 634 self.config.testargs.get("test"): 635 for class_name in expect_tests_dict.keys(): 636 for test_desc in expect_tests_dict.get(class_name, list()): 637 test = "{}#{}".format(test_desc.class_name, test_desc.test_name) 638 if test not in self.config.testargs.get("test"): 639 skip_list.append(test) 640 LOG.debug("Retry skip list: {}, total skip case: {}". 641 format(skip_list, len(skip_list))) 642 return skip_list 643 644 @classmethod 645 def _parse_suite_info(cls, suite_info): 646 tests_dict = dict() 647 test_count = 0 648 if suite_info: 649 json_str = "".join(suite_info) 650 LOG.debug("Suites info: %s" % json_str) 651 try: 652 suite_dict_list = json.loads(json_str).get("suites", []) 653 for suite_dict in suite_dict_list: 654 for class_name, test_name_dict_list in suite_dict.items(): 655 tests_dict.update({class_name.strip(): []}) 656 for test_name_dict in test_name_dict_list: 657 for test_name in test_name_dict.values(): 658 test = TestDescription(class_name.strip(), 659 test_name.strip()) 660 tests_dict.get(class_name.strip()).append(test) 661 test_count += 1 662 except json.decoder.JSONDecodeError as json_error: 663 LOG.warning("Suites info is invalid: %s" % json_error) 664 LOG.debug("Collect suite count is %s, test count is %s" % 665 (len(tests_dict), test_count)) 666 return tests_dict 667 668 def read_device_log_timeout(self, device_log_file, 669 message_list, timeout): 670 LOG.info("The timeout is {} seconds".format(timeout)) 671 pattern = "^\\d{2}-\\d{2}\\s+\\d{2}:\\d{2}:\\d{2}\\.\\d{3}\\s+(\\d+)" 672 while time.time() - self.start_time <= timeout: 673 with open(device_log_file, "r", encoding='utf-8', 674 errors='ignore') as file_read_pipe: 675 pid = "" 676 message_list.clear() 677 label_list = [[], []] # [-1, 1 ..] [line1, line2 ..] 678 suite_info = [] 679 while True: 680 try: 681 line = file_read_pipe.readline() 682 except UnicodeError as error: 683 LOG.warning("While read log file: %s" % error) 684 if not line: 685 time.sleep(5) # wait for log write to file 686 break 687 if self._is_match_marker(line): 688 if "[suites info]" in line: 689 _, pos = re.match(".+\\[suites info]", line).span() 690 suite_info.append(line[pos:].strip()) 691 692 if "[start] start run suites" in line: # 发现了任务开始标签 693 pid, is_update = \ 694 self._init_suites_start(line, pattern, pid) 695 if is_update: 696 message_list.clear() 697 label_list[0].clear() 698 label_list[1].clear() 699 if not pid or pid not in line: 700 continue 701 message_list.append(line) 702 if "[suite end]" in line: 703 label_list[0].append(-1) 704 label_list[1].append(len(message_list) - 1) 705 if "[suite start]" in line: 706 label_list[0].append(1) 707 label_list[1].append(len(message_list) - 1) 708 if "[end] run suites end" in line: 709 LOG.info("Find the end mark then analysis result") 710 LOG.debug("current JSApp pid= %s" % pid) 711 return label_list, suite_info, True 712 else: 713 LOG.error("Hjsunit run timeout {}s reached".format(timeout)) 714 LOG.debug("current JSApp pid= %s" % pid) 715 return label_list, suite_info, False 716 717 @classmethod 718 def _init_suites_start(cls, line, pattern, pid): 719 matcher = re.match(pattern, line.strip()) 720 if matcher and matcher.group(1): 721 pid = matcher.group(1) 722 return pid, True 723 return pid, False 724 725 def run_js_outer(self, request): 726 try: 727 LOG.debug("Start execute xdevice extension JSUnit Test") 728 LOG.debug("Outer version about Community") 729 self.result = os.path.join( 730 request.config.report_path, "result", 731 '.'.join((request.get_module_name(), "xml"))) 732 self.config = request.config 733 self.config.device = request.config.environment.devices[0] 734 735 config_file = request.root.source.config_file 736 suite_file = request.root.source.source_file 737 738 if not suite_file: 739 raise ParamError( 740 "test source '%s' not exists" % 741 request.root.source.source_string, error_no="00110") 742 743 LOG.debug("Test case file path: %s" % suite_file) 744 # avoid hilog service stuck issue 745 self.config.device.connector_command("shell stop_service hilogd", 746 timeout=30 * 1000) 747 self.config.device.connector_command("shell start_service hilogd", 748 timeout=30 * 1000) 749 time.sleep(10) 750 751 self.config.device.set_device_report_path(request.config.report_path) 752 self.config.device.connector_command("shell hilog -r", timeout=30 * 1000) 753 self._run_jsunit_outer(config_file, request) 754 except Exception as exception: 755 self.error_message = exception 756 if not getattr(exception, "error_no", ""): 757 setattr(exception, "error_no", "03409") 758 LOG.exception(self.error_message, exc_info=False, error_no="03409") 759 raise exception 760 finally: 761 serial = "{}_{}".format(str(self.config.device.__get_serial__()), time.time_ns()) 762 log_tar_file_name = "{}".format(str(serial).replace(":", "_")) 763 if hasattr(self.config, ConfigConst.device_log) and \ 764 self.config.device_log.get(ConfigConst.tag_enable) == ConfigConst.device_log_on: 765 self.config.device.device_log_collector.start_get_crash_log(log_tar_file_name, 766 module_name=request.get_module_name()) 767 self.config.device.device_log_collector.remove_log_address(self.device_log, self.hilog) 768 self.config.device.device_log_collector.stop_catch_device_log(self.log_proc) 769 self.config.device.device_log_collector.stop_catch_device_log(self.hilog_proc) 770 do_module_kit_teardown(request) 771 self.result = check_result_report( 772 request.config.report_path, self.result, self.error_message) 773 774 def _run_jsunit_outer(self, config_file, request): 775 if not os.path.exists(config_file): 776 LOG.error("Error: Test cases don't exist %s." % config_file) 777 raise ParamError( 778 "Error: Test cases don't exist %s." % config_file, 779 error_no="00102") 780 781 json_config = JsonParser(config_file) 782 self.kits = get_kit_instances(json_config, 783 self.config.resource_path, 784 self.config.testcases_path) 785 786 package, ability_name = self._get_driver_config_outer(json_config) 787 self.config.device.connector_command("target mount") 788 do_module_kit_setup(request, self.kits) 789 790 self.hilog = get_device_log_file( 791 request.config.report_path, 792 request.config.device.__get_serial__(), 793 "device_hilog", 794 module_name=request.get_module_name()) 795 796 hilog_open = os.open(self.hilog, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 797 0o755) 798 self.config.device.device_log_collector.add_log_address(self.device_log, self.hilog) 799 with os.fdopen(hilog_open, "a") as hilog_file_pipe: 800 if hasattr(self.config, ConfigConst.device_log) and \ 801 self.config.device_log.get(ConfigConst.tag_enable) == ConfigConst.device_log_on: 802 self.config.device.device_log_collector.clear_crash_log() 803 self.log_proc, self.hilog_proc = self.config.device.device_log_collector. \ 804 start_catch_device_log(hilog_file_pipe=hilog_file_pipe) 805 806 # execute test case 807 command = "shell aa start -d 123 -a %s -b %s" \ 808 % (ability_name, package) 809 result_value = self.config.device.connector_command(command) 810 if result_value and "start ability successfully" in \ 811 str(result_value).lower(): 812 setattr(self, "start_success", True) 813 LOG.info("execute %s's testcase success. result value=%s" 814 % (package, result_value)) 815 else: 816 LOG.info("execute %s's testcase failed. result value=%s" 817 % (package, result_value)) 818 raise RuntimeError("hjsunit test run error happened!") 819 820 self.start_time = time.time() 821 timeout_config = get_config_value('test-timeout', 822 json_config.get_driver(), 823 False, 60000) 824 timeout = int(timeout_config) / 1000 825 self.generate_console_output(request, timeout) 826 827 def _jsunit_clear_outer(self): 828 self.config.device.execute_shell_command( 829 "rm -r /%s/%s/%s/%s" % ("data", "local", "tmp", "ajur")) 830 831 def _get_driver_config_outer(self, json_config): 832 package = get_config_value('package', json_config.get_driver(), False) 833 default_ability = "{}.MainAbility".format(package) 834 ability_name = get_config_value('abilityName', json_config. 835 get_driver(), False, default_ability) 836 self.xml_output = get_xml_output(self.config, json_config) 837 timeout_config = get_config_value('native-test-timeout', 838 json_config.get_driver(), False) 839 if timeout_config: 840 self.timeout = int(timeout_config) 841 842 if not package: 843 raise ParamError("Can't find package in config file.", 844 error_no="03201") 845 return package, ability_name 846 847 def _make_exclude_list_file(self, request): 848 filter_list = [] 849 if "all-test-file-exclude-filter" in self.config.testargs: 850 json_file_list = self.config.testargs.get( 851 "all-test-file-exclude-filter") 852 self.config.testargs.pop("all-test-file-exclude-filter") 853 if not json_file_list: 854 LOG.debug("all-test-file-exclude-filter value is empty!") 855 else: 856 if not os.path.isfile(json_file_list[0]): 857 LOG.warning( 858 "[{}] is not a valid file".format(json_file_list[0])) 859 return [] 860 file_open = os.open(json_file_list[0], os.O_RDONLY, 861 stat.S_IWUSR | stat.S_IRUSR) 862 with os.fdopen(file_open, "r") as file_handler: 863 json_data = json.load(file_handler) 864 exclude_list = json_data.get( 865 DeviceTestType.jsunit_test, []) 866 867 for exclude in exclude_list: 868 if request.get_module_name() not in exclude: 869 continue 870 filter_list.extend(exclude.get(request.get_module_name())) 871 return filter_list 872 873 def _is_match_marker(self, line): 874 if self.ace_log_marker: 875 return line.lower().find(self.ace_log_marker) != -1 876 else: 877 for mark_str in _ACE_LOG_MARKER: 878 if line.lower().find(mark_str) != -1: 879 self.ace_log_marker = mark_str 880 return True 881 return False 882 883 def __result__(self): 884 return self.result if os.path.exists(self.result) else "" 885 886 887@Plugin(type=Plugin.DRIVER, id=DeviceTestType.ltp_posix_test) 888class LTPPosixTestDriver(IDriver): 889 def __init__(self): 890 self.timeout = 80 * 1000 891 self.start_time = None 892 self.result = "" 893 self.error_message = "" 894 self.kits = [] 895 self.config = None 896 self.handler = None 897 # log 898 self.hilog = None 899 self.log_proc = None 900 901 def __check_environment__(self, device_options): 902 pass 903 904 def __check_config__(self, config): 905 pass 906 907 def __execute__(self, request): 908 try: 909 LOG.debug("Start execute xdevice extension LTP Posix Test") 910 self.result = os.path.join( 911 request.config.report_path, "result", 912 '.'.join((request.get_module_name(), "xml"))) 913 self.config = request.config 914 self.config.device = request.config.environment.devices[0] 915 916 config_file = request.root.source.config_file 917 suite_file = request.root.source.source_file 918 919 if not suite_file: 920 raise ParamError( 921 "test source '%s' not exists" % 922 request.root.source.source_string, error_no="00110") 923 924 LOG.debug("Test case file path: %s" % suite_file) 925 # avoid hilog service stuck issue 926 self.config.device.connector_command("shell stop_service hilogd", 927 timeout=30 * 1000) 928 self.config.device.connector_command("shell start_service hilogd", 929 timeout=30 * 1000) 930 time.sleep(10) 931 932 self.config.device.connector_command("shell hilog -r", timeout=30 * 1000) 933 self._run_posix(config_file, request) 934 except Exception as exception: 935 self.error_message = exception 936 if not getattr(exception, "error_no", ""): 937 setattr(exception, "error_no", "03409") 938 LOG.exception(self.error_message, exc_info=True, error_no="03409") 939 raise exception 940 finally: 941 self.config.device.device_log_collector.remove_log_address(None, self.hilog) 942 self.config.device.device_log_collector.stop_catch_device_log(self.log_proc) 943 self.result = check_result_report( 944 request.config.report_path, self.result, self.error_message) 945 946 def _run_posix(self, config_file, request): 947 try: 948 if not os.path.exists(config_file): 949 LOG.error("Error: Test cases don't exist %s." % config_file) 950 raise ParamError( 951 "Error: Test cases don't exist %s." % config_file, 952 error_no="00102") 953 954 json_config = JsonParser(config_file) 955 self.kits = get_kit_instances(json_config, 956 self.config.resource_path, 957 self.config.testcases_path) 958 self.config.device.connector_command("target mount") 959 test_list = None 960 dst = None 961 for kit in self.kits: 962 test_list, dst = kit.__setup__(request.config.device, 963 request=request) 964 # apply execute right 965 self.config.device.connector_command("shell chmod -R 777 {}".format(dst)) 966 967 self.hilog = get_device_log_file( 968 request.config.report_path, 969 request.config.device.__get_serial__(), 970 "device_hilog", 971 module_name=request.get_module_name()) 972 973 hilog_open = os.open(self.hilog, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 974 0o755) 975 self.config.device.device_log_collector.add_log_address(None, self.hilog) 976 with os.fdopen(hilog_open, "a") as hilog_file_pipe: 977 _, self.log_proc = self.config.device.device_log_collector.\ 978 start_catch_device_log(hilog_file_pipe=hilog_file_pipe) 979 if hasattr(self.config, "history_report_path") and \ 980 self.config.testargs.get("test"): 981 self._do_test_retry(request, self.config.testargs) 982 else: 983 self._do_test_run(request, test_list) 984 finally: 985 do_module_kit_teardown(request) 986 987 def _do_test_retry(self, request, testargs): 988 un_pass_list = [] 989 for test in testargs.get("test"): 990 test_item = test.split("#") 991 if len(test_item) != 2: 992 continue 993 un_pass_list.append(test_item[1]) 994 LOG.debug("LTP Posix un pass list: [{}]".format(un_pass_list)) 995 self._do_test_run(request, un_pass_list) 996 997 def _do_test_run(self, request, test_list): 998 for test_bin in test_list: 999 if not test_bin.endswith(".run-test"): 1000 continue 1001 listeners = request.listeners 1002 for listener in listeners: 1003 listener.device_sn = self.config.device.device_sn 1004 parsers = get_plugin(Plugin.PARSER, 1005 "OpenSourceTest") 1006 parser_instances = [] 1007 for parser in parsers: 1008 parser_instance = parser.__class__() 1009 parser_instance.suite_name = request.root.source. \ 1010 test_name 1011 parser_instance.test_name = test_bin.replace("./", "") 1012 parser_instance.listeners = listeners 1013 parser_instances.append(parser_instance) 1014 self.handler = ShellHandler(parser_instances) 1015 self.handler.add_process_method(_ltp_output_method) 1016 result_message = self.config.device.connector_command( 1017 "shell {}".format(test_bin)) 1018 LOG.info("get result from command {}". 1019 format(result_message)) 1020 process_command_ret(result_message, self.handler) 1021 1022 def __result__(self): 1023 return self.result if os.path.exists(self.result) else "" 1024 1025 1026def _lock_screen(device): 1027 device.execute_shell_command("svc power stayon false") 1028 time.sleep(1) 1029 1030 1031def _sleep_according_to_result(result): 1032 if result: 1033 time.sleep(1) 1034 1035 1036def _ltp_output_method(handler, output, end_mark="\n"): 1037 content = output 1038 if handler.unfinished_line: 1039 content = "".join((handler.unfinished_line, content)) 1040 handler.unfinished_line = "" 1041 lines = content.split(end_mark) 1042 if content.endswith(end_mark): 1043 # get rid of the tail element of this list contains empty str 1044 return lines[:-1] 1045 else: 1046 handler.unfinished_line = lines[-1] 1047 # not return the tail element of this list contains unfinished str, 1048 # so we set position -1 1049 return lines 1050