Merge changes from topic 'qc-diag-logger-controller' into nyc-dev
* changes:
Add DiagLogger Controller Support in TelephonyBaseTest
Create a Generic DiagLogger Interface for Collecting Radio Logs
diff --git a/acts/framework/acts/controllers/diag_logger.py b/acts/framework/acts/controllers/diag_logger.py
new file mode 100644
index 0000000..96f1f03
--- /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 c6b060c..3187ca7 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 @@
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 @@
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 @@
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 @@
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: