| # Copyright 2016 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 re |
| import unittest |
| |
| from mobly import signals |
| |
| |
| # Have an instance of unittest.TestCase so we could reuse some logic from |
| # python's own unittest. |
| # _ProxyTest is required because py2 does not allow instantiating |
| # unittest.TestCase directly. |
| class _ProxyTest(unittest.TestCase): |
| def runTest(self): |
| pass |
| |
| |
| _pyunit_proxy = _ProxyTest() |
| |
| |
| def assert_equal(first, second, msg=None, extras=None): |
| """Assert the equality of objects, otherwise fail the test. |
| |
| 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. |
| """ |
| my_msg = None |
| try: |
| _pyunit_proxy.assertEqual(first, second) |
| except AssertionError as e: |
| my_msg = str(e) |
| if msg: |
| my_msg = "%s %s" % (my_msg, msg) |
| |
| # This raise statement is outside of the above except statement to prevent |
| # Python3's exception message from having two tracebacks. |
| if my_msg is not None: |
| raise signals.TestFailure(my_msg, extras=extras) |
| |
| |
| def assert_raises(expected_exception, extras=None, *args, **kwargs): |
| """Assert that an exception is raised when a function is called. |
| |
| If no exception is raised, test fail. If an exception is raised but not |
| of the expected type, the exception is let through. |
| |
| This should only be used as a context manager: |
| with assert_raises(Exception): |
| func() |
| |
| Args: |
| expected_exception: An exception class that is expected to be |
| raised. |
| extras: An optional field for extra information to be included in |
| test result. |
| """ |
| context = _AssertRaisesContext(expected_exception, extras=extras) |
| return context |
| |
| |
| def assert_raises_regex(expected_exception, |
| expected_regex, |
| extras=None, |
| *args, |
| **kwargs): |
| """Assert that an exception is raised when a function is called. |
| |
| If no exception is raised, test fail. If an exception is raised but not |
| of the expected type, the exception is let through. If an exception of the |
| expected type is raised but the error message does not match the |
| expected_regex, test fail. |
| |
| This should only be used as a context manager: |
| with assert_raises(Exception): |
| func() |
| |
| Args: |
| expected_exception: An exception class that is expected to be |
| raised. |
| extras: An optional field for extra information to be included in |
| test result. |
| """ |
| context = _AssertRaisesContext(expected_exception, |
| expected_regex, |
| extras=extras) |
| return context |
| |
| |
| def assert_true(expr, msg, extras=None): |
| """Assert an expression evaluates to true, otherwise fail the test. |
| |
| 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. |
| """ |
| if not expr: |
| fail(msg, extras) |
| |
| |
| def assert_false(expr, msg, extras=None): |
| """Assert an expression evaluates to false, otherwise fail the test. |
| |
| 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. |
| """ |
| if expr: |
| fail(msg, extras) |
| |
| |
| def skip(reason, extras=None): |
| """Skip a test. |
| |
| Args: |
| reason: The reason this test is skipped. |
| extras: An optional field for extra information to be included in |
| test result. |
| |
| Raises: |
| signals.TestSkip: Mark a test as skipped. |
| """ |
| raise signals.TestSkip(reason, extras) |
| |
| |
| def skip_if(expr, reason, extras=None): |
| """Skip a test if expression evaluates to True. |
| |
| Args: |
| expr: The expression that is evaluated. |
| reason: The reason this test is skipped. |
| extras: An optional field for extra information to be included in |
| test result. |
| """ |
| if expr: |
| skip(reason, extras) |
| |
| |
| def abort_class(reason, extras=None): |
| """Abort all subsequent tests within the same test class in one iteration. |
| |
| If one test class is requested multiple times in a test run, this can |
| only abort one of the requested executions, NOT all. |
| |
| Args: |
| reason: The reason to abort. |
| extras: An optional field for extra information to be included in |
| test result. |
| |
| Raises: |
| signals.TestAbortClass: Abort all subsequent tests in a test class. |
| """ |
| raise signals.TestAbortClass(reason, extras) |
| |
| |
| def abort_class_if(expr, reason, extras=None): |
| """Abort all subsequent tests within the same test class in one iteration, |
| if expression evaluates to True. |
| |
| If one test class is requested multiple times in a test run, this can |
| only abort one of the requested executions, NOT all. |
| |
| Args: |
| expr: The expression that is evaluated. |
| reason: The reason to abort. |
| extras: An optional field for extra information to be included in |
| test result. |
| |
| Raises: |
| signals.TestAbortClass: Abort all subsequent tests in a test class. |
| """ |
| if expr: |
| abort_class(reason, extras) |
| |
| |
| def abort_all(reason, extras=None): |
| """Abort all subsequent tests, including the ones not in this test class or |
| iteration. |
| |
| Args: |
| reason: The reason to abort. |
| extras: An optional field for extra information to be included in |
| test result. |
| |
| Raises: |
| signals.TestAbortAll: Abort all subsequent tests. |
| """ |
| raise signals.TestAbortAll(reason, extras) |
| |
| |
| def abort_all_if(expr, reason, extras=None): |
| """Abort all subsequent tests, if the expression evaluates to True. |
| |
| Args: |
| expr: The expression that is evaluated. |
| reason: The reason to abort. |
| extras: An optional field for extra information to be included in |
| test result. |
| |
| Raises: |
| signals.TestAbortAll: Abort all subsequent tests. |
| """ |
| if expr: |
| abort_all(reason, extras) |
| |
| |
| def fail(msg, extras=None): |
| """Explicitly fail a test. |
| |
| Args: |
| msg: A string explaining the details of the failure. |
| extras: An optional field for extra information to be included in |
| test result. |
| |
| Raises: |
| signals.TestFailure: Mark a test as failed. |
| """ |
| raise signals.TestFailure(msg, extras) |
| |
| |
| def explicit_pass(msg, extras=None): |
| """Explicitly pass a test. |
| |
| This will pass the test explicitly regardless of any other error happened |
| in the test body. E.g. even if errors have been recorded with `expects`, |
| the test will still be marked pass if this is called. |
| |
| A test without uncaught exception will pass implicitly so this should be |
| used scarcely. |
| |
| Args: |
| msg: A string explaining the details of the passed test. |
| extras: An optional field for extra information to be included in |
| test result. |
| |
| Raises: |
| signals.TestPass: Mark a test as passed. |
| """ |
| raise signals.TestPass(msg, extras) |
| |
| |
| class _AssertRaisesContext(object): |
| """A context manager used to implement TestCase.assertRaises* methods.""" |
| |
| def __init__(self, expected, expected_regexp=None, extras=None): |
| self.expected = expected |
| self.failureException = signals.TestFailure |
| self.expected_regexp = expected_regexp |
| self.extras = extras |
| |
| def __enter__(self): |
| return self |
| |
| def __exit__(self, exc_type, exc_value, tb): |
| if exc_type is None: |
| try: |
| exc_name = self.expected.__name__ |
| except AttributeError: |
| exc_name = str(self.expected) |
| raise signals.TestFailure('%s not raised' % exc_name, |
| extras=self.extras) |
| if not issubclass(exc_type, self.expected): |
| # let unexpected exceptions pass through |
| return False |
| self.exception = exc_value # store for later retrieval |
| if self.expected_regexp is None: |
| return True |
| |
| expected_regexp = self.expected_regexp |
| if isinstance(expected_regexp, str): |
| expected_regexp = re.compile(expected_regexp) |
| if not expected_regexp.search(str(exc_value)): |
| raise signals.TestFailure( |
| '"%s" does not match "%s"' % |
| (expected_regexp.pattern, str(exc_value)), |
| extras=self.extras) |
| return True |