| # Copyright 2017 Google Inc. |
| # |
| # 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. |
| |
| import contextlib |
| import logging |
| import time |
| |
| from mobly import asserts |
| from mobly import records |
| from mobly import signals |
| |
| # When used outside of a `base_test.BaseTestClass` context, such as when using |
| # the `android_device` controller directly, the `expects.recorder` |
| # `TestResultRecord` isn't set, which causes `expects` module methods to fail |
| # from the missing record, so this provides a default, globally accessible |
| # record for `expects` module to use as well as providing a way to get the |
| # globally recorded errors. |
| DEFAULT_TEST_RESULT_RECORD = records.TestResultRecord('mobly', 'global') |
| |
| |
| class _ExpectErrorRecorder: |
| """Singleton used to store errors caught via `expect_*` functions in test. |
| |
| This class is only instantiated once as a singleton. It holds a reference |
| to the record object for the test currently executing. |
| """ |
| |
| def __init__(self, record=None): |
| self.reset_internal_states(record=record) |
| |
| def reset_internal_states(self, record=None): |
| """Resets the internal state of the recorder. |
| |
| Args: |
| record: records.TestResultRecord, the test record for a test. |
| """ |
| self._record = None |
| self._count = 0 |
| self._record = record |
| |
| @property |
| def has_error(self): |
| """If any error has been recorded since the last reset.""" |
| return self._count > 0 |
| |
| @property |
| def error_count(self): |
| """The number of errors that have been recorded since last reset.""" |
| return self._count |
| |
| def add_error(self, error): |
| """Record an error from expect APIs. |
| |
| This method generates a position stamp for the expect. The stamp is |
| composed of a timestamp and the number of errors recorded so far. |
| |
| Args: |
| error: Exception or signals.ExceptionRecord, the error to add. |
| """ |
| self._count += 1 |
| self._record.add_error('expect@%s+%s' % (time.time(), self._count), error) |
| |
| |
| def expect_true(condition, msg, extras=None): |
| """Expects an expression evaluates to True. |
| |
| If the expectation is not met, the test is marked as fail after its |
| execution finishes. |
| |
| Args: |
| expr: The expression that is evaluated. |
| msg: A string explaining the details in case of failure. |
| extras: An optional field for extra information to be included in test |
| result. |
| """ |
| try: |
| asserts.assert_true(condition, msg, extras) |
| except signals.TestSignal as e: |
| logging.exception('Expected a `True` value, got `False`.') |
| recorder.add_error(e) |
| |
| |
| def expect_false(condition, msg, extras=None): |
| """Expects an expression evaluates to False. |
| |
| If the expectation is not met, the test is marked as fail after its |
| execution finishes. |
| |
| Args: |
| expr: The expression that is evaluated. |
| msg: A string explaining the details in case of failure. |
| extras: An optional field for extra information to be included in test |
| result. |
| """ |
| try: |
| asserts.assert_false(condition, msg, extras) |
| except signals.TestSignal as e: |
| logging.exception('Expected a `False` value, got `True`.') |
| recorder.add_error(e) |
| |
| |
| def expect_equal(first, second, msg=None, extras=None): |
| """Expects the equality of objects, otherwise fail the test. |
| |
| If the expectation is not met, the test is marked as fail after its |
| execution finishes. |
| |
| Error message is "first != second" by default. Additional explanation can |
| be supplied in the message. |
| |
| Args: |
| first: The first object to compare. |
| second: The second object to compare. |
| msg: A string that adds additional info about the failure. |
| extras: An optional field for extra information to be included in test |
| result. |
| """ |
| try: |
| asserts.assert_equal(first, second, msg, extras) |
| except signals.TestSignal as e: |
| logging.exception('Expected %s equals to %s, but they are not.', first, |
| second) |
| recorder.add_error(e) |
| |
| |
| @contextlib.contextmanager |
| def expect_no_raises(message=None, extras=None): |
| """Expects no exception is raised in a context. |
| |
| If the expectation is not met, the test is marked as fail after its |
| execution finishes. |
| |
| A default message is added to the exception `details`. |
| |
| Args: |
| message: string, custom message to add to exception's `details`. |
| extras: An optional field for extra information to be included in test |
| result. |
| """ |
| try: |
| yield |
| except Exception as e: |
| e_record = records.ExceptionRecord(e) |
| if extras: |
| e_record.extras = extras |
| msg = message or 'Got an unexpected exception' |
| details = '%s: %s' % (msg, e_record.details) |
| logging.exception(details) |
| e_record.details = details |
| recorder.add_error(e_record) |
| |
| |
| recorder = _ExpectErrorRecorder(DEFAULT_TEST_RESULT_RECORD) |