• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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('Expected %s equals to %s, but they are not.', first,
133                      second)
134    recorder.add_error(e)
135
136
137@contextlib.contextmanager
138def expect_no_raises(message=None, extras=None):
139  """Expects no exception is raised in a context.
140
141  If the expectation is not met, the test is marked as fail after its
142  execution finishes.
143
144  A default message is added to the exception `details`.
145
146  Args:
147    message: string, custom message to add to exception's `details`.
148    extras: An optional field for extra information to be included in test
149      result.
150  """
151  try:
152    yield
153  except Exception as e:
154    e_record = records.ExceptionRecord(e)
155    if extras:
156      e_record.extras = extras
157    msg = message or 'Got an unexpected exception'
158    details = '%s: %s' % (msg, e_record.details)
159    logging.exception(details)
160    e_record.details = details
161    recorder.add_error(e_record)
162
163
164recorder = _ExpectErrorRecorder(DEFAULT_TEST_RESULT_RECORD)
165