| #!/usr/bin/env python3 |
| # |
| # Copyright 2016 - Google |
| # |
| # 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. |
| """ |
| Base Class for Defining Common Telephony Test Functionality |
| """ |
| |
| import logging |
| import os |
| import re |
| import shutil |
| import traceback |
| |
| from acts import asserts |
| from acts import logger as acts_logger |
| from acts.base_test import BaseTestClass |
| from acts.controllers.android_device import DEFAULT_QXDM_LOG_PATH |
| from acts.keys import Config |
| from acts.signals import TestSignal |
| from acts.signals import TestAbortClass |
| from acts.signals import TestAbortAll |
| from acts.signals import TestBlocked |
| from acts import records |
| from acts import utils |
| |
| from acts.test_utils.tel.tel_subscription_utils import \ |
| initial_set_up_for_subid_infomation |
| from acts.test_utils.tel.tel_test_utils import abort_all_tests |
| from acts.test_utils.tel.tel_test_utils import ensure_phones_default_state |
| from acts.test_utils.tel.tel_test_utils import ensure_phones_idle |
| from acts.test_utils.tel.tel_test_utils import print_radio_info |
| from acts.test_utils.tel.tel_test_utils import reboot_device |
| from acts.test_utils.tel.tel_test_utils import refresh_sl4a_session |
| from acts.test_utils.tel.tel_test_utils import run_multithread_func |
| from acts.test_utils.tel.tel_test_utils import setup_droid_properties |
| from acts.test_utils.tel.tel_test_utils import set_phone_screen_on |
| from acts.test_utils.tel.tel_test_utils import set_phone_silent_mode |
| from acts.test_utils.tel.tel_test_utils import set_qxdm_logger_command |
| from acts.test_utils.tel.tel_test_utils import start_qxdm_loggers |
| from acts.test_utils.tel.tel_test_utils import stop_qxdm_loggers |
| from acts.test_utils.tel.tel_test_utils import unlock_sim |
| from acts.test_utils.tel.tel_defines import PRECISE_CALL_STATE_LISTEN_LEVEL_BACKGROUND |
| from acts.test_utils.tel.tel_defines import PRECISE_CALL_STATE_LISTEN_LEVEL_FOREGROUND |
| from acts.test_utils.tel.tel_defines import PRECISE_CALL_STATE_LISTEN_LEVEL_RINGING |
| from acts.test_utils.tel.tel_defines import WIFI_VERBOSE_LOGGING_ENABLED |
| from acts.test_utils.tel.tel_defines import WIFI_VERBOSE_LOGGING_DISABLED |
| |
| |
| class TelephonyBaseTest(BaseTestClass): |
| def __init__(self, controllers): |
| |
| BaseTestClass.__init__(self, controllers) |
| self.logger_sessions = [] |
| |
| self.log_path = getattr(logging, "log_path", None) |
| self.qxdm_log = self.user_params.get("qxdm_log", True) |
| qxdm_log_mask_cfg = self.user_params.get("qxdm_log_mask_cfg", None) |
| if isinstance(qxdm_log_mask_cfg, list): |
| qxdm_log_mask_cfg = qxdm_log_mask_cfg[0] |
| if qxdm_log_mask_cfg and "dev/null" in qxdm_log_mask_cfg: |
| qxdm_log_mask_cfg = None |
| stop_qxdm_loggers(self.log, self.android_devices) |
| for ad in self.android_devices: |
| try: |
| ad.adb.shell("killall -9 tcpdump") |
| except AdbError: |
| ad.log.warn("Killing existing tcpdump processes failed") |
| if not hasattr(ad, "init_log_path"): |
| ad.init_log_path = ad.log_path |
| ad.log_path = self.log_path |
| print_radio_info(ad) |
| if not unlock_sim(ad): |
| abort_all_tests(ad.log, "unable to unlock SIM") |
| ad.wakeup_screen() |
| ad.adb.shell("input keyevent 82") |
| ad.qxdm_log = getattr(ad, "qxdm_log", self.qxdm_log) |
| if ad.qxdm_log: |
| qxdm_log_mask = getattr(ad, "qxdm_log_mask", None) |
| if qxdm_log_mask_cfg: |
| qxdm_mask_path = DEFAULT_QXDM_LOG_PATH |
| ad.adb.shell("mkdir %s" % qxdm_mask_path) |
| ad.log.info("Push %s to %s", qxdm_log_mask_cfg, |
| qxdm_mask_path) |
| ad.adb.push("%s %s" % (qxdm_log_mask_cfg, qxdm_mask_path)) |
| mask_file_name = os.path.split(qxdm_log_mask_cfg)[-1] |
| qxdm_log_mask = os.path.join(qxdm_mask_path, |
| mask_file_name) |
| set_qxdm_logger_command(ad, mask=qxdm_log_mask) |
| ad.adb.pull( |
| "/firmware/image/qdsp6m.qdb %s" % ad.init_log_path, |
| ignore_status=True) |
| |
| start_qxdm_loggers(self.log, self.android_devices, |
| utils.get_current_epoch_time()) |
| self.skip_reset_between_cases = self.user_params.get( |
| "skip_reset_between_cases", True) |
| |
| # Use for logging in the test cases to facilitate |
| # faster log lookup and reduce ambiguity in logging. |
| @staticmethod |
| def tel_test_wrap(fn): |
| def _safe_wrap_test_case(self, *args, **kwargs): |
| test_id = "%s:%s:%s" % (self.__class__.__name__, self.test_name, |
| self.log_begin_time.replace(' ', '-')) |
| self.test_id = test_id |
| self.result_detail = "" |
| tries = 2 if self.user_params.get("telephony_auto_rerun") else 1 |
| for ad in self.android_devices: |
| ad.log_path = self.log_path |
| for i in range(tries): |
| result = True |
| if i > 1: |
| log_string = "[Test Case] RERUN %s" % test_name |
| self.teardown_test() |
| self.setup_test() |
| self.log.info(log_string) |
| for ad in self.android_devices: |
| try: |
| ad.droid.logI(log_string) |
| except Exception as e: |
| ad.log.warning(e) |
| try: |
| result = fn(self, *args, **kwargs) |
| except (TestSignal, TestAbortClass, TestAbortAll) as signal: |
| if self.result_detail: |
| signal.details = self.result_detail |
| raise |
| except Exception as e: |
| self.log.error(traceback.format_exc()) |
| asserts.fail(self.result_detail) |
| if result is not False: break |
| if self.user_params.get("check_crash", True): |
| new_crash = ad.check_crash_report(self.test_name, |
| self.begin_time, True) |
| if new_crash: |
| msg = "Find new crash reports %s" % new_crash |
| ad.log.error(msg) |
| self.result_detail = "%s %s %s" % (self.result_detail, |
| ad.serial, msg) |
| result = False |
| if result is not False: |
| asserts.explicit_pass(self.result_detail) |
| else: |
| asserts.fail(self.result_detail) |
| |
| return _safe_wrap_test_case |
| |
| def setup_class(self): |
| sim_conf_file = self.user_params.get("sim_conf_file") |
| if not sim_conf_file: |
| self.log.info("\"sim_conf_file\" is not provided test bed config!") |
| else: |
| if isinstance(sim_conf_file, list): |
| sim_conf_file = sim_conf_file[0] |
| # If the sim_conf_file is not a full path, attempt to find it |
| # relative to the config file. |
| if not os.path.isfile(sim_conf_file): |
| sim_conf_file = os.path.join( |
| self.user_params[Config.key_config_path], sim_conf_file) |
| if not os.path.isfile(sim_conf_file): |
| self.log.error("Unable to load user config %s ", |
| sim_conf_file) |
| |
| if not self.user_params.get("Attenuator"): |
| ensure_phones_default_state(self.log, self.android_devices) |
| else: |
| ensure_phones_idle(self.log, self.android_devices) |
| for ad in self.android_devices: |
| setup_droid_properties(self.log, ad, sim_conf_file) |
| |
| # Setup VoWiFi MDN for Verizon. b/33187374 |
| build_id = ad.build_info["build_id"] |
| if "vzw" in [ |
| sub["operator"] |
| for sub in ad.telephony["subscription"].values() |
| ] and ad.is_apk_installed("com.google.android.wfcactivation"): |
| ad.log.info("setup VoWiFi MDN per b/33187374") |
| ad.adb.shell("setprop dbg.vzw.force_wfc_nv_enabled true") |
| ad.adb.shell("am start --ei EXTRA_LAUNCH_CARRIER_APP 0 -n " |
| "\"com.google.android.wfcactivation/" |
| ".VzwEmergencyAddressActivity\"") |
| # Sub ID setup |
| initial_set_up_for_subid_infomation(self.log, ad) |
| if "enable_wifi_verbose_logging" in self.user_params: |
| ad.droid.wifiEnableVerboseLogging(WIFI_VERBOSE_LOGGING_ENABLED) |
| # If device is setup already, skip the following setup procedures |
| if getattr(ad, "telephony_test_setup", None): |
| continue |
| # Disable Emergency alerts |
| # Set chrome browser start with no-first-run verification and |
| # disable-fre. Give permission to read from and write to storage. |
| for cmd in ( |
| "pm disable com.android.cellbroadcastreceiver", |
| "pm grant com.android.chrome " |
| "android.permission.READ_EXTERNAL_STORAGE", |
| "pm grant com.android.chrome " |
| "android.permission.WRITE_EXTERNAL_STORAGE", |
| "rm /data/local/chrome-command-line", |
| "am set-debug-app --persistent com.android.chrome", |
| 'echo "chrome --no-default-browser-check --no-first-run ' |
| '--disable-fre" > /data/local/tmp/chrome-command-line'): |
| ad.adb.shell(cmd) |
| |
| # Curl for 2016/7 devices |
| if not getattr(ad, "curl_capable", False): |
| try: |
| out = ad.adb.shell("/data/curl --version") |
| if not out or "not found" in out: |
| if int(ad.adb.getprop( |
| "ro.product.first_api_level")) >= 25: |
| tel_data = self.user_params.get( |
| "tel_data", "tel_data") |
| if isinstance(tel_data, list): |
| tel_data = tel_data[0] |
| curl_file_path = os.path.join(tel_data, "curl") |
| if not os.path.isfile(curl_file_path): |
| curl_file_path = os.path.join( |
| self.user_params[Config.key_config_path], |
| curl_file_path) |
| if os.path.isfile(curl_file_path): |
| ad.log.info("Pushing Curl to /data dir") |
| ad.adb.push("%s /data" % (curl_file_path)) |
| ad.adb.shell( |
| "chmod 777 /data/curl", ignore_status=True) |
| else: |
| setattr(ad, "curl_capable", True) |
| except Exception: |
| ad.log.info("Failed to push curl on this device") |
| |
| # Ensure that a test class starts from a consistent state that |
| # improves chances of valid network selection and facilitates |
| # logging. |
| try: |
| if not set_phone_screen_on(self.log, ad): |
| self.log.error("Failed to set phone screen-on time.") |
| return False |
| if not set_phone_silent_mode(self.log, ad): |
| self.log.error("Failed to set phone silent mode.") |
| return False |
| ad.droid.telephonyAdjustPreciseCallStateListenLevel( |
| PRECISE_CALL_STATE_LISTEN_LEVEL_FOREGROUND, True) |
| ad.droid.telephonyAdjustPreciseCallStateListenLevel( |
| PRECISE_CALL_STATE_LISTEN_LEVEL_RINGING, True) |
| ad.droid.telephonyAdjustPreciseCallStateListenLevel( |
| PRECISE_CALL_STATE_LISTEN_LEVEL_BACKGROUND, True) |
| except Exception as e: |
| self.log.error("Failure with %s", e) |
| setattr(ad, "telephony_test_setup", True) |
| |
| return True |
| |
| def teardown_class(self): |
| stop_qxdm_loggers(self.log, self.android_devices) |
| ensure_phones_default_state(self.log, self.android_devices) |
| try: |
| for ad in self.android_devices: |
| ad.droid.disableDevicePassword() |
| if "enable_wifi_verbose_logging" in self.user_params: |
| ad.droid.wifiEnableVerboseLogging( |
| WIFI_VERBOSE_LOGGING_DISABLED) |
| if hasattr(ad, "init_log_path"): |
| ad.log_path = ad.init_log_path |
| return True |
| except Exception as e: |
| self.log.error("Failure with %s", e) |
| |
| def setup_test(self): |
| for ad in self.android_devices: |
| for session in ad._sl4a_manager.sessions.values(): |
| ed = session.get_event_dispatcher() |
| ed.clear_all_events() |
| output = ad.adb.logcat("-t 1") |
| match = re.search(r"\d+-\d+\s\d+:\d+:\d+.\d+", output) |
| if match: |
| ad.test_log_begin_time = match.group(0) |
| if getattr(self, "qxdm_log", True): |
| start_qxdm_loggers(self.log, self.android_devices, self.begin_time) |
| if self.skip_reset_between_cases: |
| ensure_phones_idle(self.log, self.android_devices) |
| else: |
| ensure_phones_default_state(self.log, self.android_devices) |
| |
| def on_exception(self, test_name, begin_time): |
| self._pull_diag_logs(test_name, begin_time) |
| self._cleanup_logger_sessions() |
| |
| def on_fail(self, test_name, begin_time): |
| self._pull_diag_logs(test_name, begin_time) |
| self._take_bug_report(test_name, begin_time) |
| self._cleanup_logger_sessions() |
| |
| def on_blocked(self, test_name, begin_time): |
| self.on_fail(test_name, begin_time) |
| |
| def _ad_take_extra_logs(self, ad, test_name, begin_time): |
| extra_qxdm_logs_in_seconds = self.user_params.get( |
| "extra_qxdm_logs_in_seconds", 60 * 3) |
| result = True |
| if getattr(ad, "qxdm_log", True): |
| # Gather qxdm log modified 3 minutes earlier than test start time |
| if begin_time: |
| qxdm_begin_time = begin_time - 1000 * extra_qxdm_logs_in_seconds |
| else: |
| qxdm_begin_time = None |
| try: |
| ad.get_qxdm_logs(test_name, qxdm_begin_time) |
| except Exception as e: |
| ad.log.error("Failed to get QXDM log for %s with error %s", |
| test_name, e) |
| result = False |
| |
| try: |
| ad.check_crash_report(test_name, begin_time, log_crash_report=True) |
| except Exception as e: |
| ad.log.error("Failed to check crash report for %s with error %s", |
| test_name, e) |
| result = False |
| |
| log_begin_time = getattr( |
| ad, "test_log_begin_time", None |
| ) or acts_logger.epoch_to_log_line_timestamp(begin_time - 1000 * 60) |
| log_path = os.path.join(self.log_path, test_name, |
| "%s_%s.logcat" % (ad.serial, begin_time)) |
| try: |
| ad.adb.logcat( |
| 'b all -d -v year -t "%s" > %s' % (log_begin_time, log_path), |
| timeout=120) |
| except Exception as e: |
| ad.log.error("Failed to get logcat with error %s", e) |
| result = False |
| return result |
| |
| def _take_bug_report(self, test_name, begin_time): |
| if self._skip_bug_report(): |
| return |
| dev_num = getattr(self, "number_of_devices", None) or len( |
| self.android_devices) |
| tasks = [(self._ad_take_bugreport, (ad, test_name, begin_time)) |
| for ad in self.android_devices[:dev_num]] |
| tasks.extend([(self._ad_take_extra_logs, (ad, test_name, begin_time)) |
| for ad in self.android_devices[:dev_num]]) |
| run_multithread_func(self.log, tasks) |
| for ad in self.android_devices[:dev_num]: |
| if getattr(ad, "reboot_to_recover", False): |
| reboot_device(ad) |
| ad.reboot_to_recover = False |
| if not self.user_params.get("zip_log", False): return |
| src_dir = os.path.join(self.log_path, test_name) |
| file_name = "%s_%s" % (src_dir, begin_time) |
| self.log.info("Zip folder %s to %s.zip", src_dir, file_name) |
| shutil.make_archive(file_name, "zip", src_dir) |
| shutil.rmtree(src_dir) |
| |
| def _block_all_test_cases(self, tests): |
| """Over-write _block_all_test_case in BaseTestClass.""" |
| for (i, (test_name, test_func)) in enumerate(tests): |
| signal = TestBlocked("Failed class setup") |
| record = records.TestResultRecord(test_name, self.TAG) |
| record.test_begin() |
| # mark all test cases as FAIL |
| record.test_fail(signal) |
| self.results.add_record(record) |
| # only gather bug report for the first test case |
| if i == 0: |
| self.on_fail(test_name, record.begin_time) |
| |
| def on_pass(self, test_name, begin_time): |
| self._cleanup_logger_sessions() |
| |
| def get_stress_test_number(self): |
| """Gets the stress_test_number param from user params. |
| |
| Gets the stress_test_number param. If absent, returns default 100. |
| """ |
| return int(self.user_params.get("stress_test_number", 100)) |