blob: 5fb269a37413b4e769feda9a4d3fa99a9b4be3be [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright 2017 - 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.
from acts import signals
def test_info(predicate=None, **keyed_info):
"""Adds info about test.
Extra Info to include about the test. This info will be available in the
test output. Note that if a key is given multiple times it will be added
as a list of all values. If multiples of these are stacked there results
will be merged.
Example:
# This test will have a variable my_var
@test_info(my_var='THIS IS MY TEST')
def my_test(self):
return False
Args:
predicate: A func to call that if false will skip adding this test
info. Function signature is bool(test_obj, args, kwargs)
**keyed_info: The key, value info to include in the extras for this
test.
"""
def test_info_decoractor(func):
return _TestInfoDecoratorFunc(func, predicate, keyed_info)
return test_info_decoractor
def test_tracker_info(uuid, extra_environment_info=None, predicate=None):
"""Decorator for adding test tracker info to tests results.
Will add test tracker info inside of Extras/test_tracker_info.
Example:
# This test will be linked to test tracker uuid abcd
@test_tracker_info(uuid='abcd')
def my_test(self):
return False
Args:
uuid: The uuid of the test case in test tracker.
extra_environment_info: Extra info about the test tracker environment.
predicate: A func that if false when called will ignore this info.
"""
return test_info(
test_tracker_uuid=uuid,
test_tracker_enviroment_info=extra_environment_info,
predicate=predicate)
class _TestInfoDecoratorFunc(object):
"""Object that acts as a function decorator test info."""
def __init__(self, func, predicate, keyed_info):
self.func = func
self.predicate = predicate
self.keyed_info = keyed_info
self.__name__ = func.__name__
def __get__(self, instance, owner):
"""Called by Python to create a binding for an instance closure.
When called by Python this object will create a special binding for
that instance. That binding will know how to interact with this
specific decorator.
"""
return _TestInfoBinding(self, instance)
def __call__(self, *args, **kwargs):
"""
When called runs the underlying func and then attaches test info
to a signal.
"""
try:
result = self.func(*args, **kwargs)
if result or result is None:
new_signal = signals.TestPass('')
else:
new_signal = signals.TestFailure('')
except Exception as signal:
new_signal = signal
if getattr(new_signal, "extras", None) is None:
setattr(new_signal, "extras", {})
if not isinstance(new_signal.extras, dict):
raise ValueError('test_info can only append to signal data '
'that has a dict as the extra value.')
gathered_extras = self._gather_local_info(None, *args, **kwargs)
for k, v in gathered_extras.items():
if k not in new_signal.extras:
new_signal.extras[k] = v
else:
if not isinstance(new_signal.extras[k], list):
new_signal.extras[k] = [new_signal.extras[k]]
new_signal.extras[k].insert(0, v)
raise new_signal
def gather(self, *args, **kwargs):
"""
Gathers the info from this decorator without invoking the underlying
function. This will also gather all child info if the underlying func
has that ability.
Returns: A dictionary of info.
"""
if hasattr(self.func, 'gather'):
extras = self.func.gather(*args, **kwargs)
else:
extras = {}
self._gather_local_info(extras, *args, **kwargs)
return extras
def _gather_local_info(self, gather_into, *args, **kwargs):
"""Gathers info from this decorator and ignores children.
Args:
gather_into: Gathers into a dictionary that already exists.
Returns: The dictionary with gathered info in it.
"""
if gather_into is None:
extras = {}
else:
extras = gather_into
if not self.predicate or self.predicate(args, kwargs):
for k, v in self.keyed_info.items():
if v and k not in extras:
extras[k] = v
elif v and k in extras:
if not isinstance(extras[k], list):
extras[k] = [extras[k]]
extras[k].insert(0, v)
return extras
class _TestInfoBinding(object):
"""
When Python creates an instance of an object it creates a binding object
for each closure that contains what the instance variable should be when
called. This object is a similar binding for _TestInfoDecoratorFunc.
When Python tries to create a binding of a _TestInfoDecoratorFunc it
will return one of these objects to hold the instance for that closure.
"""
def __init__(self, target, instance):
"""
Args:
target: The target for creating a binding to.
instance: The instance to bind the target with.
"""
self.target = target
self.instance = instance
self.__name__ = target.__name__
def __call__(self, *args, **kwargs):
"""
When this object is called it will call the target with the bound
instance.
"""
return self.target(self.instance, *args, **kwargs)
def gather(self, *args, **kwargs):
"""
Will gather the target with the bound instance.
"""
return self.target.gather(self.instance, *args, **kwargs)