• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 copy
20import os
21import sys
22import traceback
23
24from xdevice import StateRecorder
25from xdevice import LifeCycle
26from xdevice import ResultCode
27from xdevice import get_cst_time
28from xdevice import Scheduler
29from xdevice import LifeStage
30
31from devicetest.runner.prepare import PrepareHandler
32from devicetest.core.error_message import ErrorMessage
33from devicetest.core.constants import RunResult
34from devicetest.utils.util import calculate_execution_time
35from devicetest.utils.util import clean_sys_resource
36from devicetest.utils.util import get_base_name
37from devicetest.utils.util import get_dir_path
38from devicetest.utils.util import import_from_file
39from devicetest.core.variables import DeccVariable
40from devicetest.core.variables import ProjectVariables
41from devicetest.core.variables import CurCase
42from devicetest.core.exception import DeviceTestError
43from devicetest.core.test_case import DeviceRoot
44from devicetest.report.generation import add_log_caching_handler
45from devicetest.report.generation import del_log_caching_handler
46from devicetest.report.generation import get_caching_logs
47from devicetest.report.generation import generate_report
48
49
50class TestRunner:
51    """executes test cases and
52    """
53
54    def __init__(self):
55        self.run_list = None
56        self.no_run_list = None
57        self.running = None
58        self.configs = None
59        self.devices = None
60        self.log = None
61        self.start_time = None
62        self.test_results = None
63        self.upload_result_handler = None
64        self.project = None
65        self.prepare = None
66        self.cur_case = None
67
68    def init_pipeline_runner(self, run_list, configs, devices, log, upload_result_handler):
69        self.run_list = run_list
70        self.no_run_list = copy.copy(self.run_list)
71        self.running = False
72        self.configs = configs
73        self.devices = devices
74        self.log = log
75        self.start_time = get_cst_time()
76        self.test_results = []
77        self.upload_result_handler = upload_result_handler
78        self.project = ProjectVariables(self.log)
79        self.prepare = None
80        self.__init_project_variables()
81
82    def __init_project_variables(self):
83        """
84        testargs:为xDevice透传过来的数据,用户调用CONFIG可获取
85        :return:
86        """
87        self.log.debug("configs:{}".format(self.configs))
88        testcases_path = self.configs.get('testcases_path', "")
89        testargs = self.configs.get("testargs", {})
90        self.__flash_run_list(testargs)
91
92        self.cur_case = CurCase(self.log)
93        self.project.set_project_path()
94        self.project.set_testcase_path(testcases_path)
95        self.project.set_task_report_dir(
96            self.get_report_dir_path(self.configs))
97        self.project.set_resource_path(self.get_local_resource_path())
98
99    def get_local_resource_path(self):
100        local_resource_path = os.path.join(
101            self.project.project_path, "testcases", "DeviceTest", "resource")
102        return local_resource_path
103
104    def get_report_dir_path(self, configs):
105        report_path = configs.get("report_path")
106        if report_path and isinstance(report_path, str):
107            report_dir_path = report_path.split(os.sep.join(["", "temp", "task"]))[0]
108            if os.path.exists(report_dir_path):
109                return report_dir_path
110            self.log.debug("report dir path:{}".format(report_dir_path))
111        self.log.debug("report path:{}".format(report_path))
112        self.log.error(ErrorMessage.Error_01433.Message.en,
113                       Error_no=ErrorMessage.Error_01433.Code)
114        raise DeviceTestError(ErrorMessage.Error_01433.Topic)
115
116    def get_local_aw_path(self):
117        local_aw_path = os.path.join(
118            self.project.project_path, "testcases", "DeviceTest", "aw")
119        return local_aw_path
120
121    def __flash_run_list(self, testargs):
122        """
123        retry 场景更新run list
124        :param testargs:
125        :return:
126        """
127        get_test = testargs.get("test")
128        self.log.info("get test:{}".format(get_test))
129        retry_test_list = self.parse_retry_test_list(get_test)
130        if retry_test_list is not None:
131            self.run_list = retry_test_list
132            self.no_run_list = copy.copy(self.run_list)
133            self.log.info("retry test list:{}".format(retry_test_list))
134
135    def parse_retry_test_list(self, retry_test_list):
136        if retry_test_list is None:
137            return None
138        elif not isinstance(retry_test_list, list):
139            self.log.error(ErrorMessage.Error_01430.Message.en,
140                           error_no=ErrorMessage.Error_01430.Code)
141            raise DeviceTestError(ErrorMessage.Error_01430.Topic)
142
143        elif len(retry_test_list) == 1 and "#" not in str(retry_test_list[0]):
144            return None
145        else:
146            history_case_list = []
147            history_case_dict = dict()
148            retry_case_list = []
149            for abd_file_path in self.run_list:
150                base_file_name = get_base_name(abd_file_path)
151                if base_file_name not in history_case_dict.keys():
152                    history_case_dict.update({base_file_name: []})
153                history_case_dict.get(base_file_name).append(abd_file_path)
154                history_case_list.append(base_file_name)
155            self.log.debug("history case list:{}".format(history_case_list))
156
157            for _value in retry_test_list:
158                case_id = str(_value).split("#")[0]
159                if case_id in history_case_dict.keys():
160                    retry_case_list.append(history_case_dict.get(case_id)[0])
161            return retry_case_list
162
163    def parse_config(self, test_configs):
164        pass
165
166    def add_value_to_configs(self):
167        self.configs["log"] = self.log
168        self.configs["devices"] = self.devices
169        self.configs["project"] = self.project
170
171    def run(self):
172        self._pipeline_run()
173
174    def _pipeline_run(self):
175        self.running = True
176        aw_path = self.add_aw_path_to_sys(self.project.aw_path)
177        self.log.info("Executing run list {}.".format(self.run_list))
178
179        self.add_value_to_configs()
180
181        self.prepare = PrepareHandler(self.log, self.cur_case,
182                                      self.project, self.configs,
183                                      self.devices, self.run_list)
184        # **********混合root和非root**************
185        try:
186            for device in self.devices:
187                if hasattr(device, "is_hw_root"):
188                    DeviceRoot.is_root_device = device.is_hw_root
189                    self.log.debug(DeviceRoot.is_root_device)
190                    setattr(device, "is_device_root", DeviceRoot.is_root_device)
191
192        except Exception as e:
193            self.log.error(f'set branch api error. {e}')
194        # **************混合root和非root end**********************
195        self.prepare.run_prepare()
196
197        for test_cls_name in self.run_list:
198            case_name = get_base_name(test_cls_name)
199            if self.project.record.is_shutdown(raise_exception=False):
200                break
201            self.log.info("Executing test class {}".format(test_cls_name))
202            self.project.execute_case_name = case_name
203            self.run_test_class(test_cls_name, case_name)
204        self.prepare.run_prepare(is_teardown=True)
205        clean_sys_resource(file_path=aw_path)
206        DeccVariable.reset()
207
208    def add_aw_path_to_sys(self, aw_path):
209
210        sys_aw_path = os.path.dirname(aw_path)
211        if os.path.exists(sys_aw_path):
212            sys.path.insert(1, sys_aw_path)
213            self.log.info("add {} to sys path.".format(sys_aw_path))
214            return sys_aw_path
215        return None
216
217    def run_test_class(self, test_cls_name, case_name, time_out=0):
218        """Instantiates and executes a test class.
219        If the test cases list is not None, all the test cases in the test
220        class should be executed.
221        Args:
222            test_cls_name: Name of the test class to execute.
223        Returns:
224            A tuple, with the number of cases passed at index 0, and the total
225            number of test cases at index 1.
226        """
227        # 开始收集日志
228        case_log_buffer_hdl = add_log_caching_handler()
229
230        tests = "__init__"
231        case_result = RunResult.FAILED
232        start_time = get_cst_time()
233        case_dir_path = get_dir_path(test_cls_name)
234        test_cls_instance = None
235        try:
236            self.project.cur_case_full_path = test_cls_name
237            DeccVariable.set_cur_case_obj(self.cur_case)
238            test_cls = import_from_file(case_dir_path, case_name)
239            self.log.info("Success to import {}.".format(case_name))
240            with test_cls(self.configs) as test_cls_instance:
241                self.cur_case.set_case_instance(test_cls_instance)
242                test_cls_instance.run()
243
244            tests = test_cls_instance.tests
245            start_time = test_cls_instance.start_time
246
247            case_result = test_cls_instance.result
248            error_msg = test_cls_instance.error_msg
249
250        except Exception as exception:
251            error_msg = "{}: {}".format(ErrorMessage.Error_01100.Topic, exception)
252            self.log.error(ErrorMessage.Error_01100.Message.en, error_no=ErrorMessage.Error_01100.Code)
253            self.log.error(error_msg)
254            self.log.error(traceback.format_exc())
255        if test_cls_instance:
256            try:
257                del test_cls_instance
258                self.log.debug("del test_cls_instance success.")
259            except Exception as exception:
260                self.log.warning("del test_cls_instance exception. {}".format(exception))
261
262        Scheduler.call_life_stage_action(stage=LifeStage.case_end,
263                                         case_name=case_name,
264                                         case_result=case_result, error_msg=error_msg)
265
266        end_time = get_cst_time()
267        extra_head = self.cur_case.get_steps_extra_head()
268        steps = self.cur_case.get_steps_info()
269        # 停止收集日志
270        del_log_caching_handler(case_log_buffer_hdl)
271        # 生成报告
272        logs = get_caching_logs(case_log_buffer_hdl)
273        case_info = {
274            "name": case_name,
275            "result": case_result,
276            "begin": start_time.strftime("%Y-%m-%d %H:%M:%S"),
277            "end": end_time.strftime("%Y-%m-%d %H:%M:%S"),
278            'elapsed': calculate_execution_time(start_time, end_time),
279            "error": error_msg,
280            "logs": logs,
281            "extra_head": extra_head,
282            "steps": steps
283        }
284        report_path = os.path.join("details", case_name + ".html")
285        to_path = os.path.join(self.project.task_report_dir, report_path)
286        generate_report(to_path, case=case_info)
287        steps.clear()
288        del case_log_buffer_hdl
289        self.cur_case.set_case_instance(None)
290        self.record_current_case_result(
291            case_name, tests, case_result, start_time, error_msg, report_path)
292        return case_result, error_msg
293
294    def record_current_case_result(self, case_name, tests, case_result,
295                                   start_time, error_msg, report):
296        test_result = self.record_cls_result(
297            case_name, tests, case_result, start_time, error_msg, report)
298        self.log.debug("test result: {}".format(test_result))
299        self.test_results.append(test_result)
300        self.upload_result_handler.report_handler.test_results.append(test_result)
301
302    def stop(self):
303        """
304        Releases resources from test run. Should be called right after run()
305        finishes.
306        """
307        if self.running:
308            self.running = False
309
310    @staticmethod
311    def record_cls_result(case_name=None, tests_step=None, result=None,
312                          start_time=None, error="", report=""):
313        dict_result = {
314            "case_name": case_name,
315            "tests_step": tests_step or "__init__",
316            "result": result or RunResult.FAILED,
317            "start_time": start_time or get_cst_time(),
318            "error": error,
319            "end_time": get_cst_time(),
320            "report": report
321        }
322        return dict_result
323
324
325class TestSuiteRunner:
326    """
327    executes test suite cases
328    """
329
330    def __init__(self, suite, configs, devices, log):
331        self.suite = suite
332        self.running = False
333        self.configs = configs
334        self.devices = devices
335        self.log = log
336        self.start_time = get_cst_time()
337        self.listeners = self.configs["listeners"]
338        self.state_machine = StateRecorder()
339        self.suite_name = ""
340
341    def add_value_to_configs(self):
342        self.configs["log"] = self.log
343        self.configs["devices"] = self.devices
344        self.configs["suite_name"] = self.suite_name
345
346    def run(self):
347        self.running = True
348        self.log.info("Executing test suite: {}.".format(self.suite))
349
350        self.suite_name = get_base_name(self.suite)
351        self.add_value_to_configs()
352        self.run_test_suite(self.suite)
353
354    def run_test_suite(self, test_cls_name):
355        """Instantiates and executes a test class.
356        If the test cases list is not None, all the test cases in the test
357        class should be executed.
358        Args:
359            test_cls_name: Name of the test class to execute.
360        Returns:
361            A tuple, with the number of cases passed at index 0, and the total
362            number of test cases at index 1.
363        """
364        error_msg = "Test resource loading error"
365        suite_dir_path = get_dir_path(test_cls_name)
366        test_cls_instance = None
367        try:
368            test_cls = import_from_file(suite_dir_path, self.suite_name)
369            self.handle_suites_started()
370            self.handle_suite_started()
371            self.log.info("Success to import {}.".format(self.suite_name))
372            self.configs["cur_suite"] = test_cls
373            with test_cls(self.configs, suite_dir_path) as test_cls_instance:
374                test_cls_instance.run()
375
376            error_msg = test_cls_instance.error_msg
377
378            self.handle_suite_ended(test_cls_instance)
379            self.handle_suites_ended()
380
381        except Exception as e:
382            self.log.debug(traceback.format_exc())
383            self.log.error("run suite error! Exception: {}".format(e))
384        if test_cls_instance:
385            try:
386                del test_cls_instance
387                self.log.debug("del test suite instance success.")
388            except Exception as e:
389                self.log.warning("del test suite instance exception. "
390                                 "Exception: {}".format(e))
391        return error_msg
392
393    def stop(self):
394        """
395        Releases resources from test run. Should be called right after run()
396        finishes.
397        """
398        if self.running:
399            self.running = False
400
401    def handle_suites_started(self):
402        self.state_machine.get_suites(reset=True)
403        test_suites = self.state_machine.get_suites()
404        test_suites.suites_name = self.suite_name
405        test_suites.test_num = 0
406        for listener in self.listeners:
407            suite_report = copy.copy(test_suites)
408            listener.__started__(LifeCycle.TestSuites, suite_report)
409
410    def handle_suites_ended(self):
411        suites = self.state_machine.get_suites()
412        suites.is_completed = True
413        for listener in self.listeners:
414            listener.__ended__(LifeCycle.TestSuites, test_result=suites,
415                               suites_name=suites.suites_name)
416
417    def handle_suite_started(self):
418        self.state_machine.suite(reset=True)
419        self.state_machine.running_test_index = 0
420        test_suite = self.state_machine.suite()
421        test_suite.suite_name = self.suite_name
422        test_suite.test_num = 0
423        for listener in self.listeners:
424            suite_report = copy.copy(test_suite)
425            listener.__started__(LifeCycle.TestSuite, suite_report)
426
427    def handle_suite_ended(self, testsuite_cls):
428        suite = self.state_machine.suite()
429        suites = self.state_machine.get_suites()
430        self.handle_one_case_result(testsuite_cls)
431        suite.is_completed = True
432        # 设置测试套的报告路径
433        suite.report = testsuite_cls.suite_report_path
434        for listener in self.listeners:
435            listener.__ended__(LifeCycle.TestSuite, copy.copy(suite), is_clear=True)
436        suites.run_time += suite.run_time
437
438    def handle_one_case_result(self, testsuite_cls):
439        status_dict = {RunResult.PASSED: ResultCode.PASSED,
440                       RunResult.FAILED: ResultCode.FAILED,
441                       RunResult.BLOCKED: ResultCode.BLOCKED,
442                       "ignore": ResultCode.SKIPPED,
443                       RunResult.FAILED: ResultCode.FAILED}
444        for case_name, case_result in testsuite_cls.case_result.items():
445            result = case_result.get("result")
446            error = case_result.get("error")
447            run_time = case_result.get("run_time")
448            report = case_result.get("report")
449
450            test_result = self.state_machine.test(reset=True)
451            test_suite = self.state_machine.suite()
452            test_result.test_class = test_suite.suite_name
453            test_result.test_name = case_name
454            test_result.code = status_dict.get(result).value
455            test_result.stacktrace = error
456            test_result.run_time = run_time
457            test_result.report = report
458            test_result.current = self.state_machine.running_test_index + 1
459
460            self.state_machine.suite().run_time += run_time
461            for listener in self.listeners:
462                listener.__started__(
463                    LifeCycle.TestCase, copy.copy(test_result))
464            test_suites = self.state_machine.get_suites()
465            test_suites.test_num += 1
466            for listener in self.listeners:
467                listener.__ended__(
468                    LifeCycle.TestCase, copy.copy(test_result))
469            self.state_machine.running_test_index += 1
470