forked from robotframework/robotframework
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherrors.py
More file actions
312 lines (234 loc) · 9.9 KB
/
errors.py
File metadata and controls
312 lines (234 loc) · 9.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# Copyright 2008-2015 Nokia Networks
# Copyright 2016- Robot Framework Foundation
#
# 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.
"""Exceptions and return codes used internally.
External libraries should not used exceptions defined here.
"""
try:
unicode
except NameError:
unicode = str
# Return codes from Robot and Rebot.
# RC below 250 is the number of failed critical tests and exactly 250
# means that number or more such failures.
INFO_PRINTED = 251 # --help or --version
DATA_ERROR = 252 # Invalid data or cli args
STOPPED_BY_USER = 253 # KeyboardInterrupt or SystemExit
FRAMEWORK_ERROR = 255 # Unexpected error
class RobotError(Exception):
"""Base class for Robot Framework errors.
Do not raise this method but use more specific errors instead.
"""
def __init__(self, message='', details=''):
Exception.__init__(self, message)
self.details = details
@property
def message(self):
return unicode(self)
class FrameworkError(RobotError):
"""Can be used when the core framework goes to unexpected state.
It is good to explicitly raise a FrameworkError if some framework
component is used incorrectly. This is pretty much same as
'Internal Error' and should of course never happen.
"""
class DataError(RobotError):
"""Used when the provided test data is invalid.
DataErrors are not caught by keywords that run other keywords
(e.g. `Run Keyword And Expect Error`).
"""
class VariableError(DataError):
"""Used when variable does not exist.
VariableErrors are caught by keywords that run other keywords
(e.g. `Run Keyword And Expect Error`).
"""
class KeywordError(DataError):
"""Used when no keyword is found or there is more than one match.
KeywordErrors are caught by keywords that run other keywords
(e.g. `Run Keyword And Expect Error`).
"""
class TimeoutError(RobotError):
"""Used when a test or keyword timeout occurs.
This exception is handled specially so that execution of the
current test is always stopped immediately and it is not caught by
keywords executing other keywords (e.g. `Run Keyword And Expect
Error`).
"""
def __init__(self, message='', test_timeout=True):
RobotError.__init__(self, message)
self.test_timeout = test_timeout
@property
def keyword_timeout(self):
return not self.test_timeout
class Information(RobotError):
"""Used by argument parser with --help or --version."""
class ExecutionStatus(RobotError):
"""Base class for exceptions communicating status in test execution."""
def __init__(self, message, test_timeout=False, keyword_timeout=False,
syntax=False, exit=False, continue_on_failure=False,
return_value=None):
if '\r\n' in message:
message = message.replace('\r\n', '\n')
from robot.utils import cut_long_message
RobotError.__init__(self, cut_long_message(message))
self.test_timeout = test_timeout
self.keyword_timeout = keyword_timeout
self.syntax = syntax
self.exit = exit
self._continue_on_failure = continue_on_failure
self.return_value = return_value
@property
def timeout(self):
return self.test_timeout or self.keyword_timeout
@property
def dont_continue(self):
return self.timeout or self.syntax or self.exit
@property
def continue_on_failure(self):
return self._continue_on_failure
@continue_on_failure.setter
def continue_on_failure(self, continue_on_failure):
self._continue_on_failure = continue_on_failure
for child in getattr(self, '_errors', []):
if child is not self:
child.continue_on_failure = continue_on_failure
def can_continue(self, teardown=False, templated=False, dry_run=False):
if dry_run:
return True
if self.syntax or self.exit or self.test_timeout:
return False
if templated:
return True
if self.keyword_timeout:
return False
if teardown:
return True
return self.continue_on_failure
def get_errors(self):
return [self]
@property
def status(self):
return 'FAIL'
class ExecutionFailed(ExecutionStatus):
"""Used for communicating failures in test execution."""
class HandlerExecutionFailed(ExecutionFailed):
def __init__(self, details):
error = details.error
timeout = isinstance(error, TimeoutError)
test_timeout = timeout and error.test_timeout
keyword_timeout = timeout and error.keyword_timeout
syntax = (isinstance(error, DataError)
and not isinstance(error, (KeywordError, VariableError)))
exit_on_failure = self._get(error, 'EXIT_ON_FAILURE')
continue_on_failure = self._get(error, 'CONTINUE_ON_FAILURE')
ExecutionFailed.__init__(self, details.message, test_timeout,
keyword_timeout, syntax, exit_on_failure,
continue_on_failure)
self.full_message = details.message
self.traceback = details.traceback
def _get(self, error, attr):
return bool(getattr(error, 'ROBOT_' + attr, False))
class ExecutionFailures(ExecutionFailed):
def __init__(self, errors, message=None):
message = message or self._format_message([e.message for e in errors])
ExecutionFailed.__init__(self, message, **self._get_attrs(errors))
self._errors = errors
def _format_message(self, messages):
if len(messages) == 1:
return messages[0]
prefix = 'Several failures occurred:'
if any(msg.startswith('*HTML*') for msg in messages):
prefix = '*HTML* ' + prefix
messages = self._format_html_messages(messages)
return '\n\n'.join(
[prefix] +
['%d) %s' % (i, m) for i, m in enumerate(messages, start=1)]
)
def _format_html_messages(self, messages):
from robot.utils import html_escape
for msg in messages:
if msg.startswith('*HTML*'):
yield msg[6:].lstrip()
else:
yield html_escape(msg)
def _get_attrs(self, errors):
return {
'test_timeout': any(e.test_timeout for e in errors),
'keyword_timeout': any(e.keyword_timeout for e in errors),
'syntax': any(e.syntax for e in errors),
'exit': any(e.exit for e in errors),
'continue_on_failure': all(e.continue_on_failure for e in errors)
}
def get_errors(self):
return self._errors
class UserKeywordExecutionFailed(ExecutionFailures):
def __init__(self, run_errors=None, teardown_errors=None):
errors = self._get_active_errors(run_errors, teardown_errors)
message = self._get_message(run_errors, teardown_errors)
ExecutionFailures.__init__(self, errors, message)
if run_errors and not teardown_errors:
self._errors = run_errors.get_errors()
else:
self._errors = [self]
def _get_active_errors(self, *errors):
return [err for err in errors if err]
def _get_message(self, run_errors, teardown_errors):
run_msg = run_errors.message if run_errors else ''
td_msg = teardown_errors.message if teardown_errors else ''
if not td_msg:
return run_msg
if not run_msg:
return 'Keyword teardown failed:\n%s' % td_msg
return '%s\n\nAlso keyword teardown failed:\n%s' % (run_msg, td_msg)
class ExecutionPassed(ExecutionStatus):
"""Base class for all exceptions communicating that execution passed.
Should not be raised directly, but more detailed exceptions used instead.
"""
def __init__(self, message=None, **kwargs):
ExecutionStatus.__init__(self, message or self._get_message(), **kwargs)
self._earlier_failures = []
def _get_message(self):
from robot.utils import printable_name
return ("Invalid '%s' usage."
% printable_name(type(self).__name__, code_style=True))
def set_earlier_failures(self, failures):
if failures:
self._earlier_failures = list(failures) + self._earlier_failures
@property
def earlier_failures(self):
if not self._earlier_failures:
return None
return ExecutionFailures(self._earlier_failures)
@property
def status(self):
return 'PASS' if not self._earlier_failures else 'FAIL'
class PassExecution(ExecutionPassed):
"""Used by 'Pass Execution' keyword."""
def __init__(self, message):
ExecutionPassed.__init__(self, message)
class ContinueForLoop(ExecutionPassed):
"""Used by 'Continue For Loop' keyword."""
class ExitForLoop(ExecutionPassed):
"""Used by 'Exit For Loop' keyword."""
class ReturnFromKeyword(ExecutionPassed):
"""Used by 'Return From Keyword' keyword."""
def __init__(self, return_value=None, failures=None):
ExecutionPassed.__init__(self, return_value=return_value)
if failures:
self.set_earlier_failures(failures)
class RemoteError(RobotError):
"""Used by Remote library to report remote errors."""
def __init__(self, message='', details='', fatal=False, continuable=False):
RobotError.__init__(self, message, details)
self.ROBOT_EXIT_ON_FAILURE = fatal
self.ROBOT_CONTINUE_ON_FAILURE = continuable