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