Skip to content

Commit d970b7a

Browse files
committed
2 parents 20e8562 + 52f4f9b commit d970b7a

File tree

5 files changed

+134
-37
lines changed

5 files changed

+134
-37
lines changed

atest/libs/failing.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,21 @@ def failure_deeper(self, rounds=10):
2222
raise RuntimeError('Finally failing')
2323
self.failure_deeper(rounds-1)
2424

25+
def continuable(self, message):
26+
self._raise_special(message, continuable=True)
27+
28+
def fatal(self, message):
29+
self._raise_special(message, fatal='yes')
30+
31+
def not_special(self, message):
32+
self._raise_special(message)
33+
34+
def _raise_special(self, message, continuable=False, fatal=False):
35+
special = AssertionError(message)
36+
special.ROBOT_CONTINUE_ON_FAILURE = continuable
37+
special.ROBOT_EXIT_ON_FAILURE = fatal
38+
raise special
39+
2540

2641
class MyException(Exception):
2742
pass
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
*** Settings ***
2+
Resource resource.robot
3+
Suite Setup Start And Import Remote Library failing.py
4+
Suite Teardown Stop Remote Library
5+
6+
*** Test Cases ***
7+
Not special
8+
[Documentation] FAIL message
9+
Not special message
10+
Fail This should not be executed
11+
12+
Continuable
13+
[Documentation] FAIL Several failures occurred:\n\n
14+
... 1) message\n\n
15+
... 2) second message\n\n
16+
... 3) third message
17+
Continuable message
18+
Continuable second message
19+
Continuable third message
20+
21+
Fatal
22+
[Documentation] FAIL Execution ends here
23+
Fatal Execution ends here
24+
Fail This should not be executed
25+
26+
Fails due to earlier fatal error
27+
[Documentation] FAIL Test execution stopped due to a fatal error.
28+
Fail This should not be executed

atest/tests/basic_communication.robot

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Passing
1313
Failing
1414
[Documentation] FAIL This is the error we get
1515
Failing This is the error we get
16+
Fail This should not be executed
1617

1718
Logging
1819
[Documentation] LOG 1 INFO Hello, world! LOG 2 WARN Warning, warning!!

src/robotremoteserver.py

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -106,21 +106,33 @@ def get_keyword_names(self):
106106

107107
def run_keyword(self, name, args, kwargs=None):
108108
args, kwargs = self._handle_binary_args(args, kwargs or {})
109-
result = {'status': 'FAIL', 'return': '', 'output': '',
110-
'error': '', 'traceback': ''}
109+
result = {'status': 'FAIL'}
111110
self._intercept_std_streams()
112111
try:
113112
return_value = self._get_keyword(name)(*args, **kwargs)
114113
except:
115-
result['error'], result['traceback'] = self._get_error_details()
114+
exc_type, exc_value, exc_tb = sys.exc_info()
115+
self._add_to_result(result, 'error',
116+
self._get_error_message(exc_type, exc_value))
117+
self._add_to_result(result, 'traceback',
118+
self._get_error_traceback(exc_tb))
119+
self._add_to_result(result, 'continuable',
120+
self._get_error_attribute(exc_value, 'CONTINUE'),
121+
default=False)
122+
self._add_to_result(result, 'fatal',
123+
self._get_error_attribute(exc_value, 'EXIT'),
124+
default=False)
116125
else:
117126
try:
118-
result['return'] = self._handle_return_value(return_value)
127+
self._add_to_result(result, 'return',
128+
self._handle_return_value(return_value))
119129
except:
120-
result['error'] = self._get_error_message()
130+
exc_type, exc_value, _ = sys.exc_info()
131+
self._add_to_result(result, 'error',
132+
self._get_error_message(exc_type, exc_value))
121133
else:
122134
result['status'] = 'PASS'
123-
result['output'] = self._restore_std_streams()
135+
self._add_to_result(result, 'output', self._restore_std_streams())
124136
return result
125137

126138
def _handle_binary_args(self, args, kwargs):
@@ -133,6 +145,10 @@ def _handle_binary_arg(self, arg):
133145
return arg.data
134146
return arg
135147

148+
def _add_to_result(self, result, key, value, default=''):
149+
if value != default:
150+
result[key] = value
151+
136152
def get_keyword_arguments(self, name):
137153
kw = self._get_keyword(name)
138154
if not kw:
@@ -167,17 +183,10 @@ def _get_keyword(self, name):
167183
return kw
168184
return None
169185

170-
def _get_error_details(self):
171-
exc_type, exc_value, exc_tb = sys.exc_info()
186+
def _get_error_message(self, exc_type, exc_value):
172187
if exc_type in self._fatal_exceptions:
173188
self._restore_std_streams()
174189
raise
175-
return (self._get_error_message(exc_type, exc_value),
176-
self._get_error_traceback(exc_tb))
177-
178-
def _get_error_message(self, exc_type=None, exc_value=None):
179-
if exc_type is None:
180-
exc_type, exc_value = sys.exc_info()[:2]
181190
name = exc_type.__name__
182191
message = self._get_message_from_exception(exc_value)
183192
if not message:
@@ -201,6 +210,9 @@ def _get_error_traceback(self, exc_tb):
201210
trace = ''.join(traceback.format_list(entries))
202211
return 'Traceback (most recent call last):\n' + trace
203212

213+
def _get_error_attribute(self, exc_value, name):
214+
return bool(getattr(exc_value, 'ROBOT_%s_ON_FAILURE' % name, False))
215+
204216
def _handle_return_value(self, ret):
205217
if isinstance(ret, basestring):
206218
return self._handle_binary_result(ret)

utest/test_robotremoteserver.py

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,51 @@
77

88

99
class NonServingRemoteServer(RobotRemoteServer):
10+
1011
def __init__(self, library):
1112
self._library = library
1213

14+
1315
class StaticLibrary:
16+
1417
def passing_keyword(self):
1518
pass
16-
def failing_keyword(self, exception, message='Hello, world!'):
17-
raise exception(message)
19+
20+
def failing_keyword(self, exception=AssertionError, message='Hello, world!',
21+
**kwargs):
22+
err = exception(message)
23+
for name, value in kwargs.items():
24+
setattr(err, name, value)
25+
raise err
26+
27+
def returning_keyword(self, value):
28+
return value
29+
1830
def logging_keyword(self, stdout, stderr):
1931
if stdout:
2032
sys.stdout.write(stdout)
2133
if stderr:
2234
sys.stderr.write(stderr)
2335
self.streams = (sys.stdout, sys.stderr)
36+
2437
def _not_included(self):
2538
"""Starts with an underscore"""
39+
2640
not_included = "Not a method or function"
2741
not_included_2 = NonServingRemoteServer # Callable but not method/function
2842

43+
2944
class HybridLibrary:
45+
3046
def __init__(self):
3147
self.library = StaticLibrary()
48+
3249
def get_keyword_names(self):
3350
return [n for n in dir(self.library) if n.endswith('_keyword')]
51+
3452
def __getattr__(self, name):
3553
return getattr(self.library, name)
54+
3655
def not_included(self):
3756
"""Not returned by get_keyword_names"""
3857

@@ -46,43 +65,65 @@ def setUp(self):
4665
def test_get_keyword_names(self):
4766
self.assertEquals(self.server.get_keyword_names(),
4867
['failing_keyword', 'logging_keyword',
49-
'passing_keyword', 'stop_remote_server'])
68+
'passing_keyword', 'returning_keyword',
69+
'stop_remote_server'])
5070

5171
def test_run_passing_keyword(self):
52-
self.assertEquals(self.server.run_keyword('passing_keyword', []),
53-
{'status': 'PASS', 'output': '', 'traceback': '',
54-
'return': '', 'error': ''})
72+
self.assertEquals(self._run('passing_keyword'), {'status': 'PASS'})
73+
74+
def test_returning_keyword(self):
75+
for ret in 'Hello, world!', 42, True:
76+
self.assertEquals(self._run('returning_keyword', ret),
77+
{'status': 'PASS', 'return': ret})
5578

5679
def test_run_failing_keyword(self):
57-
ret = self.server.run_keyword('failing_keyword', [ValueError])
58-
self.assertEquals(ret['status'], 'FAIL')
59-
self.assertEquals(ret['error'], 'ValueError: Hello, world!')
80+
ret = self._run('failing_keyword', ValueError)
81+
self._verify_failed(ret, 'ValueError: Hello, world!')
6082

6183
def test_strip_generic_exception_names_from_error_messages(self):
6284
for exception in AssertionError, Exception, RuntimeError:
63-
ret = self.server.run_keyword('failing_keyword', [exception])
64-
self.assertEquals(ret['status'], 'FAIL')
65-
self.assertEquals(ret['error'], 'Hello, world!')
85+
ret = self._run('failing_keyword', exception)
86+
self._verify_failed(ret, 'Hello, world!')
6687

6788
def test_return_only_exception_name_if_no_error_message(self):
6889
for exception in AssertionError, ValueError:
69-
ret = self.server.run_keyword('failing_keyword', [exception, ''])
70-
self.assertEquals(ret['status'], 'FAIL')
71-
self.assertEquals(ret['error'], exception.__name__)
90+
ret = self._run('failing_keyword', exception, '')
91+
self._verify_failed(ret, exception.__name__)
92+
93+
def test_continuable_error(self):
94+
ret = self._run('failing_keyword', ROBOT_CONTINUE_ON_FAILURE=True)
95+
self._verify_failed(ret, continuable=True)
96+
97+
def test_fatal_error(self):
98+
ret = self._run('failing_keyword', ROBOT_EXIT_ON_FAILURE='yes please')
99+
self._verify_failed(ret, fatal=True)
72100

73101
def test_logging_to_stdout(self):
74-
ret = self.server.run_keyword('logging_keyword', ['out', ''])
75-
self.assertEquals(ret['output'], 'out')
76-
self.assertTrue(all(s.closed for s in self.library.streams))
102+
ret = self._run('logging_keyword', 'out', '')
103+
self._verify_logged(ret, 'out')
77104

78105
def test_logging_to_stderr(self):
79-
ret = self.server.run_keyword('logging_keyword', ['', 'err'])
80-
self.assertEquals(ret['output'], 'err')
81-
self.assertTrue(all(s.closed for s in self.library.streams))
106+
ret = self._run('logging_keyword', '', 'err')
107+
self._verify_logged(ret, 'err')
82108

83109
def test_logging_to_stdout_and_stderr(self):
84-
ret = self.server.run_keyword('logging_keyword', ['out', 'err'])
85-
self.assertEquals(ret['output'], 'out\n*INFO* err')
110+
ret = self._run('logging_keyword', 'out', 'err')
111+
self._verify_logged(ret, 'out\n*INFO* err')
112+
113+
def _run(self, kw, *args, **kwargs):
114+
return self.server.run_keyword(kw, args, kwargs)
115+
116+
def _verify_failed(self, ret, error='Hello, world!', continuable=False,
117+
fatal=False):
118+
ret.pop('traceback')
119+
if continuable:
120+
self.assertEquals(ret.pop('continuable'), True)
121+
if fatal:
122+
self.assertEquals(ret.pop('fatal'), True)
123+
self.assertEquals(ret, {'error': error, 'status': 'FAIL'})
124+
125+
def _verify_logged(self, ret, output):
126+
self.assertEquals(ret, {'output': output, 'status': 'PASS'})
86127
self.assertTrue(all(s.closed for s in self.library.streams))
87128

88129

0 commit comments

Comments
 (0)