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 20from ohos.drivers import * 21from ohos.error import ErrorMessage 22 23__all__ = ["CppTestDriver"] 24LOG = platform_logger("CppTestDriver") 25DEFAULT_TEST_PATH = "/%s/%s/" % ("data", "test") 26 27FAILED_RUN_TEST_ATTEMPTS = 3 28TIME_OUT = 900 * 1000 29 30 31@Plugin(type=Plugin.DRIVER, id=DeviceTestType.cpp_test) 32class CppTestDriver(IDriver): 33 """ 34 CppTestDriver is a Test that runs a native test package on given harmony 35 device. 36 """ 37 38 def __init__(self): 39 self.result = "" 40 self.error_message = "" 41 self.config = None 42 self.rerun = True 43 self.rerun_all = True 44 self.runner = None 45 # log 46 self.device_log = None 47 self.hilog = None 48 self.log_proc = None 49 self.hilog_proc = None 50 51 def __check_environment__(self, device_options): 52 pass 53 54 def __check_config__(self, config): 55 pass 56 57 def __execute__(self, request): 58 try: 59 LOG.debug("Start execute xdevice extension CppTest") 60 61 self.config = request.config 62 self.config.device = request.config.environment.devices[0] 63 64 config_file = request.root.source.config_file 65 self.result = "%s.xml" % \ 66 os.path.join(request.config.report_path, 67 "result", request.root.source.test_name) 68 69 self.device_log = get_device_log_file( 70 request.config.report_path, 71 request.config.device.__get_serial__(), 72 "device_log", 73 module_name=request.get_module_name(), 74 repeat=request.config.repeat, 75 repeat_round=request.get_repeat_round()) 76 77 self.hilog = get_device_log_file( 78 request.config.report_path, 79 request.config.device.__get_serial__(), 80 "device_hilog", 81 module_name=request.get_module_name(), 82 repeat=request.config.repeat, 83 repeat_round=request.get_repeat_round()) 84 85 device_log_open = os.open(self.device_log, os.O_WRONLY | os.O_CREAT | 86 os.O_APPEND, FilePermission.mode_755) 87 hilog_open = os.open(self.hilog, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 88 FilePermission.mode_755) 89 self.config.device.device_log_collector.add_log_address(self.device_log, self.hilog) 90 log_level = self.config.device_log.get(ConfigConst.tag_loglevel, "INFO") 91 with os.fdopen(device_log_open, "a") as log_file_pipe, \ 92 os.fdopen(hilog_open, "a") as hilog_file_pipe: 93 self.log_proc, self.hilog_proc = self.config.device.device_log_collector. \ 94 start_catch_device_log(log_file_pipe, hilog_file_pipe, log_level=log_level) 95 self._run_cpp_test(config_file, listeners=request.listeners, 96 request=request) 97 log_file_pipe.flush() 98 hilog_file_pipe.flush() 99 100 except Exception as exception: 101 self.error_message = exception 102 if not getattr(exception, "error_no", ""): 103 setattr(exception, "error_no", "03404") 104 LOG.exception(self.error_message, exc_info=False, error_no="03404") 105 raise exception 106 107 finally: 108 self.config.device.device_log_collector.remove_log_address(self.device_log, self.hilog) 109 self.config.device.device_log_collector.stop_catch_device_log(self.log_proc) 110 self.config.device.device_log_collector.stop_catch_device_log(self.hilog_proc) 111 self.result = check_result_report( 112 request.config.report_path, self.result, self.error_message, request=request) 113 114 def _run_cpp_test(self, config_file, listeners=None, request=None): 115 try: 116 if not os.path.exists(config_file): 117 err_msg = ErrorMessage.Common.Code_0301002.format(config_file) 118 LOG.error(err_msg) 119 raise ParamError(err_msg) 120 121 json_config = JsonParser(config_file) 122 kits = get_kit_instances(json_config, self.config.resource_path, 123 self.config.testcases_path) 124 125 for listener in listeners: 126 listener.device_sn = self.config.device.device_sn 127 128 self._get_driver_config(json_config) 129 do_module_kit_setup(request, kits) 130 self.runner = RemoteCppTestRunner(self.config) 131 self.runner.suite_name = request.root.source.test_name 132 133 if hasattr(self.config, "history_report_path") and \ 134 self.config.testargs.get("test"): 135 self._do_test_retry(listeners, self.config.testargs) 136 else: 137 gtest_para_parse(self.config.testargs, self.runner, request) 138 self._do_test_run(listeners) 139 140 finally: 141 do_module_kit_teardown(request) 142 143 def _do_test_retry(self, listener, testargs): 144 for test in testargs.get("test"): 145 test_item = test.split("#") 146 if len(test_item) != 2: 147 continue 148 self.runner.add_instrumentation_arg( 149 "gtest_filter", "%s.%s" % (test_item[0], test_item[1])) 150 self.runner.run(listener) 151 152 def _do_test_run(self, listener): 153 test_to_run = self._collect_test_to_run() 154 LOG.info("Collected test count is: %s" % (len(test_to_run) 155 if test_to_run else 0)) 156 if not test_to_run: 157 self.runner.run(listener) 158 else: 159 self._run_with_rerun(listener, test_to_run) 160 161 def _collect_test_to_run(self): 162 if self.rerun: 163 self.runner.add_instrumentation_arg("gtest_list_tests", True) 164 run_results = self.runner.dry_run() 165 self.runner.remove_instrumentation_arg("gtest_list_tests") 166 return run_results 167 return None 168 169 def _run_tests(self, listener): 170 test_tracker = CollectingTestListener() 171 listener_copy = listener.copy() 172 listener_copy.append(test_tracker) 173 self.runner.run(listener_copy) 174 test_run = test_tracker.get_current_run_results() 175 return test_run 176 177 def _run_with_rerun(self, listener, expected_tests): 178 LOG.debug("Ready to run with rerun, expect run: %s" 179 % len(expected_tests)) 180 test_run = self._run_tests(listener) 181 LOG.debug("Run with rerun, has run: %s" % len(test_run) 182 if test_run else 0) 183 if len(test_run) < len(expected_tests): 184 expected_tests = TestDescription.remove_test(expected_tests, 185 test_run) 186 if not expected_tests: 187 LOG.debug("No tests to re-run, all tests executed at least " 188 "once.") 189 if self.rerun_all: 190 self._rerun_all(expected_tests, listener) 191 else: 192 self._rerun_serially(expected_tests, listener) 193 194 def _rerun_all(self, expected_tests, listener): 195 tests = [] 196 for test in expected_tests: 197 tests.append("%s.%s" % (test.class_name, test.test_name)) 198 self.runner.add_instrumentation_arg("gtest_filter", ":".join(tests)) 199 LOG.debug("Ready to rerun file, expect run: %s" % len(expected_tests)) 200 test_run = self._run_tests(listener) 201 LOG.debug("Rerun file, has run: %s" % len(test_run)) 202 if len(test_run) < len(expected_tests): 203 expected_tests = TestDescription.remove_test(expected_tests, 204 test_run) 205 if not expected_tests: 206 LOG.debug("Rerun textFile success") 207 self._rerun_serially(expected_tests, listener) 208 209 def _rerun_serially(self, expected_tests, listener): 210 LOG.debug("Rerun serially, expected run: %s" % len(expected_tests)) 211 for test in expected_tests: 212 self.runner.add_instrumentation_arg( 213 "gtest_filter", "%s.%s" % (test.class_name, test.test_name)) 214 self.runner.rerun(listener, test) 215 self.runner.remove_instrumentation_arg("gtest_filter") 216 217 def _get_driver_config(self, json_config): 218 target_test_path = get_config_value('native-test-device-path', 219 json_config.get_driver(), False) 220 if target_test_path: 221 self.config.target_test_path = target_test_path 222 else: 223 self.config.target_test_path = DEFAULT_TEST_PATH 224 225 self.config.module_name = get_config_value( 226 'module-name', json_config.get_driver(), False) 227 228 timeout_config = get_config_value('native-test-timeout', 229 json_config.get_driver(), False) 230 if timeout_config: 231 self.config.timeout = int(timeout_config) 232 else: 233 self.config.timeout = TIME_OUT 234 235 rerun = get_config_value('rerun', json_config.get_driver(), False) 236 if isinstance(rerun, bool): 237 self.rerun = rerun 238 elif str(rerun).lower() == "false": 239 self.rerun = False 240 else: 241 self.rerun = True 242 243 def __result__(self): 244 return self.result if os.path.exists(self.result) else "" 245 246 247class RemoteCppTestRunner: 248 def __init__(self, config): 249 self.arg_list = {} 250 self.suite_name = None 251 self.config = config 252 self.rerun_attempt = FAILED_RUN_TEST_ATTEMPTS 253 # 判断半容器 254 self.ohca = check_device_ohca(self.config.device) 255 256 def dry_run(self): 257 parsers = get_plugin(Plugin.PARSER, CommonParserType.cpptest_list) 258 if parsers: 259 parsers = parsers[:1] 260 parser_instances = [] 261 for parser in parsers: 262 parser_instance = parser.__class__() 263 parser_instances.append(parser_instance) 264 handler = ShellHandler(parser_instances) 265 handler.add_process_method(_cpp_output_method) 266 267 command = "cd %s; chmod +x *; ./%s %s --gtest_color=no" \ 268 % (self.config.target_test_path, self.config.module_name, 269 self.get_args_command()) 270 if self.ohca: 271 bin_path = "{}/{}".format(self.config.target_test_path, 272 self.config.module_name) 273 # 半容器的C++用例需要调整权限 274 command = "ohsh toybox chown 20000000:20000000 {}; ohsh toybox chmod a+x {}; ohsh {} {}". \ 275 format(bin_path, bin_path, bin_path, self.get_args_command()) 276 277 self.config.device.execute_shell_command( 278 command, timeout=self.config.timeout, receiver=handler, retry=0) 279 return parser_instances[0].tests 280 281 def run(self, listener): 282 handler = self._get_shell_handler(listener) 283 command = "cd %s; chmod +x *; ./%s %s --gtest_color=no" \ 284 % (self.config.target_test_path, self.config.module_name, 285 self.get_args_command()) 286 if self.ohca: 287 bin_path = "{}/{}".format(self.config.target_test_path, 288 self.config.module_name) 289 # 半容器的C++用例需要调整权限 290 command = "ohsh toybox chown 20000000:20000000 {}; ohsh toybox chmod a+x {}; ohsh {} {}". \ 291 format(bin_path, bin_path, bin_path, self.get_args_command()) 292 293 self.config.device.execute_shell_command( 294 command, timeout=self.config.timeout, receiver=handler, retry=0) 295 296 def rerun(self, listener, test): 297 if self.rerun_attempt: 298 test_tracker = CollectingTestListener() 299 listener_copy = listener.copy() 300 listener_copy.append(test_tracker) 301 handler = self._get_shell_handler(listener_copy) 302 try: 303 command = "cd %s; chmod +x *; ./%s %s" \ 304 % (self.config.target_test_path, 305 self.config.module_name, 306 self.get_args_command()) 307 if self.ohca: 308 bin_path = "{}/{}".format(self.config.target_test_path, 309 self.config.module_name) 310 command = "ohsh toybox chmod a+x {}; ohsh {} {}".format( 311 bin_path, bin_path, self.get_args_command()) 312 313 self.config.device.execute_shell_command( 314 command, timeout=self.config.timeout, receiver=handler, 315 retry=0) 316 317 except ShellCommandUnresponsiveException as _: 318 LOG.debug("Exception: ShellCommandUnresponsiveException") 319 finally: 320 if not test_tracker.get_current_run_results(): 321 LOG.debug("No test case is obtained finally") 322 self.rerun_attempt -= 1 323 handler.parsers[0].mark_test_as_blocked(test) 324 else: 325 LOG.debug("Not execute and mark as blocked finally") 326 handler = self._get_shell_handler(listener) 327 handler.parsers[0].mark_test_as_blocked(test) 328 329 def add_instrumentation_arg(self, name, value): 330 if not name or not value: 331 return 332 self.arg_list[name] = value 333 334 def remove_instrumentation_arg(self, name): 335 if not name: 336 return 337 if name in self.arg_list: 338 del self.arg_list[name] 339 340 def get_args_command(self): 341 args_commands = "" 342 for key, value in self.arg_list.items(): 343 if key == "gtest_list_tests": 344 args_commands = "%s --%s" % (args_commands, key) 345 else: 346 args_commands = "%s --%s=%s" % (args_commands, key, value) 347 return args_commands 348 349 def _get_shell_handler(self, listener): 350 parsers = get_plugin(Plugin.PARSER, CommonParserType.cpptest) 351 if parsers: 352 parsers = parsers[:1] 353 parser_instances = [] 354 for parser in parsers: 355 parser_instance = parser.__class__() 356 parser_instance.suite_name = self.suite_name 357 parser_instance.listeners = listener 358 parser_instances.append(parser_instance) 359 handler = ShellHandler(parser_instances) 360 handler.add_process_method(_cpp_output_method) 361 return handler 362 363 364def _cpp_output_method(handler, output, end_mark="\n"): 365 content = output 366 if handler.unfinished_line: 367 content = "".join((handler.unfinished_line, content)) 368 handler.unfinished_line = "" 369 lines = content.split(end_mark) 370 if content.endswith(end_mark): 371 # get rid of the tail element of this list contains empty str 372 return lines[:-1] 373 else: 374 handler.unfinished_line = lines[-1] 375 # not return the tail element of this list contains unfinished str, 376 # so we set position -1 377 return lines[:-1] 378