• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3# Copyright 2017 - 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
17from acts import signals
18
19def test_info(predicate=None, **keyed_info):
20    """Adds info about test.
21
22    Extra Info to include about the test. This info will be available in the
23    test output. Note that if a key is given multiple times it will be added
24    as a list of all values. If multiples of these are stacked there results
25    will be merged.
26
27    Example:
28        # This test will have a variable my_var
29        @test_info(my_var='THIS IS MY TEST')
30        def my_test(self):
31            return False
32
33    Args:
34        predicate: A func to call that if false will skip adding this test
35                   info. Function signature is bool(test_obj, args, kwargs)
36        **keyed_info: The key, value info to include in the extras for this
37                      test.
38    """
39
40    def test_info_decoractor(func):
41        return _TestInfoDecoratorFunc(func, predicate, keyed_info)
42
43    return test_info_decoractor
44
45
46def test_tracker_info(uuid, extra_environment_info=None, predicate=None):
47    """Decorator for adding test tracker info to tests results.
48
49    Will add test tracker info inside of Extras/test_tracker_info.
50
51    Example:
52        # This test will be linked to test tracker uuid abcd
53        @test_tracker_info(uuid='abcd')
54        def my_test(self):
55            return False
56
57    Args:
58        uuid: The uuid of the test case in test tracker.
59        extra_environment_info: Extra info about the test tracker environment.
60        predicate: A func that if false when called will ignore this info.
61    """
62    return test_info(
63        test_tracker_uuid=uuid,
64        test_tracker_enviroment_info=extra_environment_info,
65        predicate=predicate)
66
67
68class _TestInfoDecoratorFunc(object):
69    """Object that acts as a function decorator test info."""
70
71    def __init__(self, func, predicate, keyed_info):
72        self.func = func
73        self.predicate = predicate
74        self.keyed_info = keyed_info
75        self.__name__ = func.__name__
76
77    def __get__(self, instance, owner):
78        """Called by Python to create a binding for an instance closure.
79
80        When called by Python this object will create a special binding for
81        that instance. That binding will know how to interact with this
82        specific decorator.
83        """
84        return _TestInfoBinding(self, instance)
85
86    def __call__(self, *args, **kwargs):
87        """
88        When called runs the underlying func and then attaches test info
89        to a signal.
90        """
91        try:
92            result = self.func(*args, **kwargs)
93
94            if result or result is None:
95                new_signal = signals.TestPass('')
96            else:
97                new_signal = signals.TestFailure('')
98        except Exception as signal:
99            new_signal = signal
100
101        if getattr(new_signal, "extras", None) is None:
102            setattr(new_signal, "extras", {})
103        if not isinstance(new_signal.extras, dict):
104            raise ValueError('test_info can only append to signal data '
105                             'that has a dict as the extra value.')
106
107        gathered_extras = self._gather_local_info(None, *args, **kwargs)
108        for k, v in gathered_extras.items():
109            if k not in new_signal.extras:
110                new_signal.extras[k] = v
111            else:
112                if not isinstance(new_signal.extras[k], list):
113                    new_signal.extras[k] = [new_signal.extras[k]]
114
115                new_signal.extras[k].insert(0, v)
116
117        raise new_signal
118
119    def gather(self, *args, **kwargs):
120        """
121        Gathers the info from this decorator without invoking the underlying
122        function. This will also gather all child info if the underlying func
123        has that ability.
124
125        Returns: A dictionary of info.
126        """
127        if hasattr(self.func, 'gather'):
128            extras = self.func.gather(*args, **kwargs)
129        else:
130            extras = {}
131
132        self._gather_local_info(extras, *args, **kwargs)
133
134        return extras
135
136    def _gather_local_info(self, gather_into, *args, **kwargs):
137        """Gathers info from this decorator and ignores children.
138
139        Args:
140            gather_into: Gathers into a dictionary that already exists.
141
142        Returns: The dictionary with gathered info in it.
143        """
144        if gather_into is None:
145            extras = {}
146        else:
147            extras = gather_into
148        if not self.predicate or self.predicate(args, kwargs):
149            for k, v in self.keyed_info.items():
150                if v and k not in extras:
151                    extras[k] = v
152                elif v and k in extras:
153                    if not isinstance(extras[k], list):
154                        extras[k] = [extras[k]]
155                    extras[k].insert(0, v)
156
157        return extras
158
159
160class _TestInfoBinding(object):
161    """
162    When Python creates an instance of an object it creates a binding object
163    for each closure that contains what the instance variable should be when
164    called. This object is a similar binding for _TestInfoDecoratorFunc.
165    When Python tries to create a binding of a _TestInfoDecoratorFunc it
166    will return one of these objects to hold the instance for that closure.
167    """
168
169    def __init__(self, target, instance):
170        """
171        Args:
172            target: The target for creating a binding to.
173            instance: The instance to bind the target with.
174        """
175        self.target = target
176        self.instance = instance
177        self.__name__ = target.__name__
178
179    def __call__(self, *args, **kwargs):
180        """
181        When this object is called it will call the target with the bound
182        instance.
183        """
184        return self.target(self.instance, *args, **kwargs)
185
186    def gather(self, *args, **kwargs):
187        """
188        Will gather the target with the bound instance.
189        """
190        return self.target.gather(self.instance, *args, **kwargs)
191