1# Copyright 2017 Google Inc. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import contextlib 16import logging 17import time 18 19from mobly import asserts 20from mobly import records 21from mobly import signals 22 23# When used outside of a `base_test.BaseTestClass` context, such as when using 24# the `android_device` controller directly, the `expects.recorder` 25# `TestResultRecord` isn't set, which causes `expects` module methods to fail 26# from the missing record, so this provides a default, globally accessible 27# record for `expects` module to use as well as providing a way to get the 28# globally recorded errors. 29DEFAULT_TEST_RESULT_RECORD = records.TestResultRecord('mobly', 'global') 30 31 32class _ExpectErrorRecorder: 33 """Singleton used to store errors caught via `expect_*` functions in test. 34 35 This class is only instantiated once as a singleton. It holds a reference 36 to the record object for the test currently executing. 37 """ 38 39 def __init__(self, record=None): 40 self.reset_internal_states(record=record) 41 42 def reset_internal_states(self, record=None): 43 """Resets the internal state of the recorder. 44 45 Args: 46 record: records.TestResultRecord, the test record for a test. 47 """ 48 self._record = None 49 self._count = 0 50 self._record = record 51 52 @property 53 def has_error(self): 54 """If any error has been recorded since the last reset.""" 55 return self._count > 0 56 57 @property 58 def error_count(self): 59 """The number of errors that have been recorded since last reset.""" 60 return self._count 61 62 def add_error(self, error): 63 """Record an error from expect APIs. 64 65 This method generates a position stamp for the expect. The stamp is 66 composed of a timestamp and the number of errors recorded so far. 67 68 Args: 69 error: Exception or signals.ExceptionRecord, the error to add. 70 """ 71 self._count += 1 72 self._record.add_error('expect@%s+%s' % (time.time(), self._count), error) 73 74 75def expect_true(condition, msg, extras=None): 76 """Expects an expression evaluates to True. 77 78 If the expectation is not met, the test is marked as fail after its 79 execution finishes. 80 81 Args: 82 expr: The expression that is evaluated. 83 msg: A string explaining the details in case of failure. 84 extras: An optional field for extra information to be included in test 85 result. 86 """ 87 try: 88 asserts.assert_true(condition, msg, extras) 89 except signals.TestSignal as e: 90 logging.exception('Expected a `True` value, got `False`.') 91 recorder.add_error(e) 92 93 94def expect_false(condition, msg, extras=None): 95 """Expects an expression evaluates to False. 96 97 If the expectation is not met, the test is marked as fail after its 98 execution finishes. 99 100 Args: 101 expr: The expression that is evaluated. 102 msg: A string explaining the details in case of failure. 103 extras: An optional field for extra information to be included in test 104 result. 105 """ 106 try: 107 asserts.assert_false(condition, msg, extras) 108 except signals.TestSignal as e: 109 logging.exception('Expected a `False` value, got `True`.') 110 recorder.add_error(e) 111 112 113def expect_equal(first, second, msg=None, extras=None): 114 """Expects the equality of objects, otherwise fail the test. 115 116 If the expectation is not met, the test is marked as fail after its 117 execution finishes. 118 119 Error message is "first != second" by default. Additional explanation can 120 be supplied in the message. 121 122 Args: 123 first: The first object to compare. 124 second: The second object to compare. 125 msg: A string that adds additional info about the failure. 126 extras: An optional field for extra information to be included in test 127 result. 128 """ 129 try: 130 asserts.assert_equal(first, second, msg, extras) 131 except signals.TestSignal as e: 132 logging.exception( 133 'Expected %s equals to %s, but they are not.', first, second 134 ) 135 recorder.add_error(e) 136 137 138@contextlib.contextmanager 139def expect_no_raises(message=None, extras=None): 140 """Expects no exception is raised in a context. 141 142 If the expectation is not met, the test is marked as fail after its 143 execution finishes. 144 145 A default message is added to the exception `details`. 146 147 Args: 148 message: string, custom message to add to exception's `details`. 149 extras: An optional field for extra information to be included in test 150 result. 151 """ 152 try: 153 yield 154 except Exception as e: 155 e_record = records.ExceptionRecord(e) 156 if extras: 157 e_record.extras = extras 158 msg = message or 'Got an unexpected exception' 159 details = '%s: %s' % (msg, e_record.details) 160 logging.exception(details) 161 e_record.details = details 162 recorder.add_error(e_record) 163 164 165recorder = _ExpectErrorRecorder(DEFAULT_TEST_RESULT_RECORD) 166