• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3#   Copyright 2018 - The Android Open Source Project
4#
5#   Licensed under the Apache License, Version 2.0 (the "License");
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of the License at
8#
9#       http://www.apache.org/licenses/LICENSE-2.0
10#
11#   Unless required by applicable law or agreed to in writing, software
12#   distributed under the License is distributed on an "AS IS" BASIS,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16
17"""Base test action class, provide a base class for representing a collection of
18test actions.
19"""
20
21import datetime
22import inspect
23import time
24
25from acts.controllers.buds_lib import tako_trace_logger
26from acts.libs.utils.timer import TimeRecorder
27
28# All methods start with "_" are considered hidden.
29DEFAULT_HIDDEN_ACTION_PREFIX = '_'
30
31
32def timed_action(method):
33    """A common decorator for test actions."""
34
35    def timed(self, *args, **kw):
36        """Log the enter/exit/time of the action method."""
37        func_name = self._convert_default_action_name(method.__name__)
38        if not func_name:
39            func_name = method.__name__
40        self.log_step('%s...' % func_name)
41        self.timer.start_timer(func_name, True)
42        result = method(self, *args, **kw)
43        # TODO: Method run time collected can be used for automatic KPI checks
44        self.timer.stop_timer(func_name)
45        return result
46
47    return timed
48
49
50class TestActionNotFoundError(Exception):
51    pass
52
53
54class BaseTestAction(object):
55    """Class for organizing a collection of test actions.
56
57    Test actions are just normal python methods, and should perform a specified
58    action. @timed_action decorator can log the entry/exit of the test action,
59    and the execution time.
60
61    The BaseTestAction class also provides a mapping between human friendly
62    names and test action methods in order to support configuration base
63    execution. By default, all methods not hidden (not start with "_") is
64    exported as human friendly name by replacing "_" with space.
65
66    Test action method can be called directly, or via
67    _perform_action(<human friendly name>, <args...>)
68    method.
69    """
70
71    @classmethod
72    def _fill_default_action_map(cls):
73        """Parse current class and get all test actions methods."""
74        # a <human readable name>:<method name> map.
75        cls._action_map = dict()
76        for name, _ in inspect.getmembers(cls, inspect.ismethod):
77            act_name = cls._convert_default_action_name(name)
78            if act_name:
79                cls._action_map[act_name] = name
80
81    @classmethod
82    def _convert_default_action_name(cls, func_name):
83        """Default conversion between method name -> human readable action name.
84        """
85        if not func_name.startswith(DEFAULT_HIDDEN_ACTION_PREFIX):
86            act_name = func_name.lower()
87            act_name = act_name.replace('_', ' ')
88            act_name = act_name.title()
89            return act_name.strip()
90        else:
91            return ''
92
93    @classmethod
94    def _add_action_alias(cls, default_act_name, alias):
95        """Add an alias to an existing test action."""
96        if default_act_name in cls._action_map:
97            cls._action_map[alias] = cls._action_map[default_act_name]
98            return True
99        else:
100            return False
101
102    @classmethod
103    def _get_action_names(cls):
104        if not hasattr(cls, '_action_map'):
105            cls._fill_default_action_map()
106        return cls._action_map.keys()
107
108    @classmethod
109    def get_current_time_logcat_format(cls):
110        return datetime.datetime.now().strftime('%m-%d %H:%M:%S.000')
111
112    @classmethod
113    def _action_exists(cls, action_name):
114        """Verify if an human friendly action name exists or not."""
115        if not hasattr(cls, '_action_map'):
116            cls._fill_default_action_map()
117        return action_name in cls._action_map
118
119    @classmethod
120    def _validate_actions(cls, action_list):
121        """Verify if an human friendly action name exists or not.
122
123        Args:
124          :param action_list: list of actions to be validated.
125
126        Returns:
127          tuple of (is valid, list of invalid/non-existent actions)
128        """
129        not_found = []
130        for action_name in action_list:
131            if not cls._action_exists(action_name):
132                not_found.append(action_name)
133        all_valid = False if not_found else True
134        return all_valid, not_found
135
136    def __init__(self, logger=None):
137        if logger is None:
138            self.logger = tako_trace_logger.TakoTraceLogger()
139            self.log_step = self.logger.step
140        else:
141            self.logger = logger
142            self.log_step = self.logger.info
143        self.timer = TimeRecorder()
144        self._fill_default_action_map()
145
146    def __enter__(self):
147        return self
148
149    def __exit__(self, *args):
150        pass
151
152    def _perform_action(self, action_name, *args, **kwargs):
153        """Perform the specified human readable action."""
154        if action_name not in self._action_map:
155            raise TestActionNotFoundError('Action %s not found this class.'
156                                          % action_name)
157
158        method = self._action_map[action_name]
159        ret = getattr(self, method)(*args, **kwargs)
160        return ret
161
162    @timed_action
163    def print_actions(self):
164        """Example action methods.
165
166        All test action method must:
167            1. return a value. False means action failed, any other value means
168               pass.
169            2. should not start with "_". Methods start with "_" is hidden.
170        All test action method may:
171            1. have optional arguments. Mutable argument can be used to pass
172               value
173            2. raise exceptions. Test case class is expected to handle
174               exceptions
175        """
176        num_acts = len(self._action_map)
177
178        self.logger.info('I can do %d action%s:' %
179                      (num_acts, 's' if num_acts != 1 else ''))
180        for act in self._action_map.keys():
181            self.logger.info(' - %s' % act)
182        return True
183
184    @timed_action
185    def sleep(self, seconds):
186        self.logger.info('%s seconds' % seconds)
187        time.sleep(seconds)
188
189
190if __name__ == '__main__':
191    acts = BaseTestAction()
192    acts.print_actions()
193    acts._perform_action('print actions')
194    print(acts._get_action_names())
195