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 signals.TestSignal as signal: 99 new_signal = signal 100 except Exception as cause: 101 new_signal = signals.TestError(cause) 102 103 if new_signal.extras is None: 104 new_signal.extras = {} 105 if not isinstance(new_signal.extras, dict): 106 raise ValueError('test_info can only append to signal data ' 107 'that has a dict as the extra value.') 108 109 gathered_extras = self._gather_local_info(None, *args, **kwargs) 110 for k, v in gathered_extras.items(): 111 if k not in new_signal.extras: 112 new_signal.extras[k] = v 113 else: 114 if not isinstance(new_signal.extras[k], list): 115 new_signal.extras[k] = [new_signal.extras[k]] 116 117 new_signal.extras[k].insert(0, v) 118 119 raise new_signal 120 121 def gather(self, *args, **kwargs): 122 """ 123 Gathers the info from this decorator without invoking the underlying 124 function. This will also gather all child info if the underlying func 125 has that ability. 126 127 Returns: A dictionary of info. 128 """ 129 if hasattr(self.func, 'gather'): 130 extras = self.func.gather(*args, **kwargs) 131 else: 132 extras = {} 133 134 self._gather_local_info(extras, *args, **kwargs) 135 136 return extras 137 138 def _gather_local_info(self, gather_into, *args, **kwargs): 139 """Gathers info from this decorator and ignores children. 140 141 Args: 142 gather_into: Gathers into a dictionary that already exists. 143 144 Returns: The dictionary with gathered info in it. 145 """ 146 if gather_into is None: 147 extras = {} 148 else: 149 extras = gather_into 150 if not self.predicate or self.predicate(args, kwargs): 151 for k, v in self.keyed_info.items(): 152 if v and k not in extras: 153 extras[k] = v 154 elif v and k in extras: 155 if not isinstance(extras[k], list): 156 extras[k] = [extras[k]] 157 extras[k].insert(0, v) 158 159 return extras 160 161 162class _TestInfoBinding(object): 163 """ 164 When Python creates an instance of an object it creates a binding object 165 for each closure that contains what the instance variable should be when 166 called. This object is a similar binding for _TestInfoDecoratorFunc. 167 When Python tries to create a binding of a _TestInfoDecoratorFunc it 168 will return one of these objects to hold the instance for that closure. 169 """ 170 171 def __init__(self, target, instance): 172 """ 173 Args: 174 target: The target for creating a binding to. 175 instance: The instance to bind the target with. 176 """ 177 self.target = target 178 self.instance = instance 179 self.__name__ = target.__name__ 180 181 def __call__(self, *args, **kwargs): 182 """ 183 When this object is called it will call the target with the bound 184 instance. 185 """ 186 return self.target(self.instance, *args, **kwargs) 187 188 def gather(self, *args, **kwargs): 189 """ 190 Will gather the target with the bound instance. 191 """ 192 return self.target.gather(self.instance, *args, **kwargs) 193