diff options
author | Nathan Harold <nharold@google.com> | 2016-06-23 00:40:38 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2016-06-23 00:40:39 +0000 |
commit | 1b6e8738db729e8e86d173bcdec3d5ad7961d2f5 (patch) | |
tree | 23cb3c1e0632308d6e4305219e136f9fdc12c5d4 | |
parent | 610c1a13e751d220671707058d9edeafbf6c4f90 (diff) | |
parent | f631c4a41309c22da961857ce0fd482e04127f24 (diff) | |
download | platform_tools_test_connectivity-1b6e8738db729e8e86d173bcdec3d5ad7961d2f5.tar.gz platform_tools_test_connectivity-1b6e8738db729e8e86d173bcdec3d5ad7961d2f5.tar.bz2 platform_tools_test_connectivity-1b6e8738db729e8e86d173bcdec3d5ad7961d2f5.zip |
Merge changes from topic 'qc-diag-logger-controller' into nyc-devnougat-dev
* changes:
Add DiagLogger Controller Support in TelephonyBaseTest
Create a Generic DiagLogger Interface for Collecting Radio Logs
-rw-r--r-- | acts/framework/acts/controllers/diag_logger.py | 159 | ||||
-rw-r--r-- | acts/framework/acts/test_utils/tel/TelephonyBaseTest.py | 34 |
2 files changed, 191 insertions, 2 deletions
diff --git a/acts/framework/acts/controllers/diag_logger.py b/acts/framework/acts/controllers/diag_logger.py new file mode 100644 index 0000000000..96f1f0331f --- /dev/null +++ b/acts/framework/acts/controllers/diag_logger.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3.4 +# +# Copyright 2016 - 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 + +ACTS_CONTROLLER_CONFIG_NAME = "DiagLogger" +ACTS_CONTROLLER_REFERENCE_NAME = "diag_logger" + + +class DiagLoggerError(Exception): + """This is the base exception class for errors generated by + DiagLogger modules. + """ + pass + + +def create(configs, logger): + """Initializes the Diagnotic Logger instances based on the + provided JSON configuration(s). The expected keys are: + + Package: A package name containing the diagnostic logger + module. It should be in python path of the environment. + Type: A first-level type for the Logger, which should correspond + the name of the module containing the Logger implementation. + SubType: The exact implementation of the sniffer, which should + correspond to the name of the class to be used. + HostLogPath: This is the default directory used to dump any logs + that are captured or any other files that are stored as part + of the logging process. It's use is implementation specific, + but it should be provided by all loggers for completeness. + Configs: A dictionary specifying baseline configurations of the + particular Logger. These configurations may be overridden at + the start of a session. + """ + objs = [] + for c in configs: + diag_package_name = c["Package"] # package containing module + diag_logger_type = c["Type"] # module name + diag_logger_name = c["SubType"] # class name + host_log_path = c["HostLogPath"] + base_configs = c["Configs"] + module_name = "{}.{}".format(diag_package_name, diag_logger_type) + module = importlib.import_module(module_name) + logger = getattr(module, diag_logger_name) + + objs.append(logger(host_log_path, + logger, + config_container=base_configs)) + return objs + + +def destroy(objs): + """Stops all ongoing logger sessions, deletes any temporary files, and + prepares logger objects for destruction. + """ + for diag_logger in objs: + try: + diag_logger.reset() + except DiagLoggerError: + # TODO: log if things go badly here + pass + + +class DiagLoggerBase(): + """Base Class for Proprietary Diagnostic Log Collection + + The DiagLoggerBase is a simple interface for running on-device logging via + a standard workflow that can be integrated without the caller actually + needing to know the details of what logs are captured or how. + The workflow is as follows: + + 1) Create a DiagLoggerBase Object + 2) Call start() to begin an active logging session. + 3) Call stop() to end an active logging session. + 4) Call pull() to ensure all collected logs are stored at + 'host_log_path' + 5) Call reset() to stop all logging and clear any unretrieved logs. + """ + + def __init__(self, host_log_path, logger=None, config_container=None): + """Create a Diagnostic Logging Proxy Object + + Args: + host_log_path: File path where retrieved logs should be stored + config_container: A transparent container used to pass config info + """ + self.host_log_path = os.path.realpath(os.path.expanduser( + host_log_path)) + self.config_container = config_container + if not os.path.isdir(self.host_log_path): + os.mkdir(self.host_log_path) + self.logger = logger + if not self.logger: + self.logger = logging.getLogger(self.__class__.__name__) + + def start(self, config_container=None): + """Start collecting Diagnostic Logs + + Args: + config_container: A transparent container used to pass config info + + Returns: + A logging session ID that can be later used to stop the session + For Diag interfaces supporting only one session this is unneeded + """ + raise NotImplementedError("Base class should not be invoked directly!") + + def stop(self, session_id=None): + """Stop collecting Diagnostic Logs + + Args: + session_id: an optional session id provided for multi session + logging support + + Returns: + """ + raise NotImplementedError("Base class should not be invoked directly!") + + def pull(self, session_id=None, out_path=None): + """Save all cached diagnostic logs collected to the host + + Args: + session_id: an optional session id provided for multi session + logging support + + out_path: an optional override to host_log_path for a specific set + of logs + + Returns: + An integer representing a port number on the host available for adb + forward. + """ + raise NotImplementedError("Base class should not be invoked directly!") + + def reset(self): + """Stop any ongoing logging sessions and clear any cached logs that have + not been retrieved with pull(). This must delete all session records and + return the logging object to a state equal to when constructed. + """ + raise NotImplementedError("Base class should not be invoked directly!") + + def get_log_path(self): + """Return the log path for this object""" + return self.host_log_path diff --git a/acts/framework/acts/test_utils/tel/TelephonyBaseTest.py b/acts/framework/acts/test_utils/tel/TelephonyBaseTest.py index c6b060cf77..3187ca75ee 100644 --- a/acts/framework/acts/test_utils/tel/TelephonyBaseTest.py +++ b/acts/framework/acts/test_utils/tel/TelephonyBaseTest.py @@ -20,6 +20,9 @@ import os import time import traceback + +import acts.controllers.diag_logger + from acts.base_test import BaseTestClass from acts.signals import TestSignal from acts import utils @@ -51,6 +54,7 @@ from acts.utils import force_airplane_mode class TelephonyBaseTest(BaseTestClass): def __init__(self, controllers): BaseTestClass.__init__(self, controllers) + self.logger_sessions = [] # Use for logging in the test cases to facilitate # faster log lookup and reduce ambiguity in logging. @@ -105,6 +109,9 @@ class TelephonyBaseTest(BaseTestClass): return _safe_wrap_test_case def setup_class(self): + setattr(self, "diag_logger", + self.register_controller(acts.controllers.diag_logger, + required=False)) for ad in self.android_devices: setup_droid_properties(self.log, ad, self.user_params["sim_conf_file"]) @@ -163,17 +170,39 @@ class TelephonyBaseTest(BaseTestClass): def setup_test(self): for ad in self.android_devices: refresh_droid_config(self.log, ad) + + if getattr(self, "diag_logger", None): + for logger in self.diag_logger: + self.log.info("Starting a diagnostic session {}".format( + logger)) + self.logger_sessions.append((logger, logger.start())) + return ensure_phones_default_state(self.log, self.android_devices) def teardown_test(self): + for (logger, session) in self.logger_sessions: + self.log.info("Resetting a diagnostic session {},{}".format( + logger, session)) + logger.reset() + self.logger_sessions = [] return True def on_exception(self, test_name, begin_time): + self._pull_diag_logs(test_name, begin_time) return self._take_bug_report(test_name, begin_time) def on_fail(self, test_name, begin_time): + self._pull_diag_logs(test_name, begin_time) return self._take_bug_report(test_name, begin_time) + def _pull_diag_logs(self, test_name, begin_time): + for (logger, session) in self.logger_sessions: + self.log.info("Pulling diagnostic session {}".format(logger)) + logger.stop(session) + diag_path = os.path.join(self.log_path, begin_time) + utils.create_dir(diag_path) + logger.pull(session, diag_path) + def _take_bug_report(self, test_name, begin_time): if "no_bug_report_on_fail" in self.user_params: return @@ -184,8 +213,9 @@ class TelephonyBaseTest(BaseTestClass): try: ad.adb.wait_for_device() ad.take_bug_report(test_name, begin_time) - tombstone_path = os.path.join(ad.log_path, "BugReports", - "{},{}".format(begin_time, ad.serial).replace(' ','_')) + tombstone_path = os.path.join( + ad.log_path, "BugReports", + "{},{}".format(begin_time, ad.serial).replace(' ', '_')) utils.create_dir(tombstone_path) ad.adb.pull('/data/tombstones/', tombstone_path) except: |