1#
2# Copyright (C) 2016 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16"""This module is where all the test signal classes and related utilities live.
17"""
18
19import functools
20import json
21import logging
22
23
24def GeneratedTest(func):
25    """A decorator used to suppress result reporting for the test case that
26    kicks off a group of generated test cases.
27
28    Returns:
29        What the decorated function returns.
30    """
31
32    @functools.wraps(func)
33    def wrapper(*args, **kwargs):
34        func(*args, **kwargs)
35        raise TestSilent("Result reporting for %s is suppressed" %
36                         func.__name__)
37
38    return wrapper
39
40
41class TestSignalError(Exception):
42    """Raised when an error occurs inside a test signal."""
43
44class TestSignal(Exception):
45    """Base class for all test result control signals.
46
47    Attributes:
48        details: A string that describes the reason for raising this signal.
49        extras: A json-serializable data type to convey extra information about
50                a test result.
51    """
52
53    def __init__(self, details, extras=None):
54        super(TestSignal, self).__init__(details)
55        try:
56            self.details = str(details)
57        except UnicodeEncodeError:
58            # TODO: remove when we stop supporting Python 2
59            logging.warning(u"Details contain non-ASCII characters: %s",
60                            details)
61            self.details = details.encode("utf-8")
62        try:
63            json.dumps(extras)
64            self.extras = extras
65        except TypeError:
66            raise TestSignalError(("Extras must be json serializable. %s "
67                                   "is not.") % extras)
68
69    def __str__(self):
70        return "Details=%s, Extras=%s" % (self.details, self.extras)
71
72
73class TestFailure(TestSignal):
74    """Raised when a test has failed."""
75
76
77class TestPass(TestSignal):
78    """Raised when a test has passed."""
79
80
81class TestSkip(TestSignal):
82    """Raised when a test has been skipped."""
83
84
85class TestSilent(TestSignal):
86    """Raised when a test should not be reported. This should only be used for
87    generated test cases.
88    """
89
90
91class TestAbortClass(TestSignal):
92    """Raised when all subsequent test cases within the same test class should
93    be aborted.
94    """
95
96
97class TestAbortAll(TestSignal):
98    """Raised when all subsequent test cases should be aborted."""
99
100
101class ControllerError(Exception):
102    """Raised when an error occured in controller classes."""
103