#!/usr/bin/env python3 # # Copyright 2019 - The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import importlib import logging import os import signal import subprocess import traceback from functools import wraps from grpc import RpcError from acts import asserts, signals from acts.context import get_current_context from acts.base_test import BaseTestClass from cert.async_subprocess_logger import AsyncSubprocessLogger from cert.os_utils import get_gd_root from cert.os_utils import read_crash_snippet_and_log_tail from cert.os_utils import is_subprocess_alive from cert.os_utils import make_ports_available from cert.os_utils import TerminalColor from cert.gd_device import MOBLY_CONTROLLER_CONFIG_NAME as CONTROLLER_CONFIG_NAME from facade import rootservice_pb2 as facade_rootservice from cert.gd_base_test_lib import setup_class_core from cert.gd_base_test_lib import teardown_class_core from cert.gd_base_test_lib import setup_test_core from cert.gd_base_test_lib import teardown_test_core from cert.gd_base_test_lib import dump_crashes_core class GdBaseTestClass(BaseTestClass): SUBPROCESS_WAIT_TIMEOUT_SECONDS = 10 def setup_class(self, dut_module, cert_module): self.log_path_base = get_current_context().get_full_output_path() self.verbose_mode = bool(self.user_params.get('verbose_mode', False)) for config in self.controller_configs[CONTROLLER_CONFIG_NAME]: config['verbose_mode'] = self.verbose_mode self.info = setup_class_core( dut_module=dut_module, cert_module=cert_module, verbose_mode=self.verbose_mode, log_path_base=self.log_path_base, controller_configs=self.controller_configs) self.dut_module = self.info['dut_module'] self.cert_module = self.info['cert_module'] self.rootcanal_running = self.info['rootcanal_running'] self.rootcanal_logpath = self.info['rootcanal_logpath'] self.rootcanal_process = self.info['rootcanal_process'] if 'rootcanal' in self.controller_configs: asserts.assert_true(self.info['rootcanal_exist'], "Root canal does not exist at %s" % self.info['rootcanal']) asserts.assert_true(self.info['make_rootcanal_ports_available'], "Failed to make root canal ports available") self.log.debug("Running %s" % " ".join(self.info['rootcanal_cmd'])) asserts.assert_true( self.info['is_rootcanal_process_started'], msg="Cannot start root-canal at " + str(self.info['rootcanal'])) asserts.assert_true(self.info['is_subprocess_alive'], msg="root-canal stopped immediately after running") self.rootcanal_logger = self.info['rootcanal_logger'] self.controller_configs = self.info['controller_configs'] # Parse and construct GD device objects self.register_controller(importlib.import_module('cert.gd_device'), builtin=True) self.dut = self.gd_devices[1] self.cert = self.gd_devices[0] def teardown_class(self): teardown_class_core( rootcanal_running=self.rootcanal_running, rootcanal_process=self.rootcanal_process, rootcanal_logger=self.rootcanal_logger, subprocess_wait_timeout_seconds=self.SUBPROCESS_WAIT_TIMEOUT_SECONDS) def setup_test(self): setup_test_core(dut=self.dut, cert=self.cert, dut_module=self.dut_module, cert_module=self.cert_module) def teardown_test(self): teardown_test_core(cert=self.cert, dut=self.dut) def __getattribute__(self, name): attr = super().__getattribute__(name) if not callable(attr) or not GdBaseTestClass.__is_entry_function(name): return attr @wraps(attr) def __wrapped(*args, **kwargs): try: return attr(*args, **kwargs) except RpcError as e: exception_info = "".join(traceback.format_exception(e.__class__, e, e.__traceback__)) raise signals.TestFailure( "RpcError during test\n\nRpcError:\n\n%s\n%s" % (exception_info, self.__dump_crashes())) return __wrapped __ENTRY_METHODS = {"setup_class", "teardown_class", "setup_test", "teardown_test"} @staticmethod def __is_entry_function(name): return name.startswith("test_") or name in GdBaseTestClass.__ENTRY_METHODS def __dump_crashes(self): """ return: formatted stack traces if found, or last few lines of log """ crash_detail = dump_crashes_core( dut=self.dut, cert=self.cert, rootcanal_running=self.rootcanal_running, rootcanal_process=self.rootcanal_process, rootcanal_logpath=self.rootcanal_logpath) return crash_detail