Skip to content

Commit 0781725

Browse files
pekkaklarckspooning
authored andcommitted
Intercept also stderr when running keywords.
Fixes robotframework#2.
1 parent 07f8c1c commit 0781725

4 files changed

Lines changed: 80 additions & 17 deletions

File tree

atest/libs/logging.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1+
import sys
2+
3+
14
class Logging(object):
25

3-
def logging(self, message, level='', evaluate=False):
4-
if evaluate:
6+
def logging(self, message, level='', evaluate=False, stderr=False):
7+
if evaluate and evaluate != 'False':
58
message = eval(message)
69
if level:
710
message = '*%s* %s' % (level, message)
8-
print message
11+
stream = sys.stdout if not stderr else sys.stderr
12+
stream.write(message + '\n')
913

1014
def multiple_messages_with_different_levels(self):
1115
print 'Info message'
@@ -25,9 +29,12 @@ def logging_and_returning(self, logged, returned):
2529
print logged
2630
return returned
2731

32+
def logging_both_to_stdout_and_stderr(self, *messages):
33+
for index, msg in enumerate(messages):
34+
stream = sys.stdout if index % 2 == 0 else sys.stderr
35+
stream.write(msg)
2836

2937
if __name__ == '__main__':
30-
import sys
3138
from robotremoteserver import RobotRemoteServer
3239

3340
RobotRemoteServer(Logging(), '127.0.0.1', *sys.argv[1:])

atest/tests/logging.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,25 @@ Logging and returning
6060
Should Be Equal ${ret} This is returned
6161
${ret} = Logging This keyword returns nothing
6262
Should Be Equal ${ret} ${EMPTY}
63+
64+
Logging through stderr
65+
[Documentation]
66+
... LOG 1 INFO Hello, stderr!
67+
... LOG 2 DEBUG Hyvä \u2603
68+
... LOG 3 INFO 0\n1
69+
Logging Hello, stderr! stderr=yes
70+
Logging Hyvä \u2603 level=DEBUG stderr=yes
71+
Logging '0\\x00\\n1\\x01' evaluate=yes stderr=yes
72+
73+
Logging both through stdout and stderr
74+
[Documentation]
75+
... LOG 1:1 INFO stdout
76+
... LOG 1:2 INFO stderr
77+
... LOG 2:1 DEBUG stdout-continue
78+
... LOG 2:2 INFO stderr
79+
... LOG 3:1 INFO o\no2
80+
... LOG 3:2 DEBUG e
81+
... LOG 3:3 INFO e2
82+
Logging both to stdout and stderr stdout stderr
83+
Logging both to stdout and stderr *DEBUG* stdout stderr -continue
84+
Logging both to stdout and stderr o\n *DEBUG* e\n o2 *INFO* e2

src/robotremoteserver.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def run_keyword(self, name, args, kwargs=None):
105105
args, kwargs = self._handle_binary_args(args, kwargs or {})
106106
result = {'status': 'FAIL', 'return': '', 'output': '',
107107
'error': '', 'traceback': ''}
108-
self._intercept_stdout()
108+
self._intercept_std_streams()
109109
try:
110110
return_value = self._get_keyword(name)(*args, **kwargs)
111111
except:
@@ -117,7 +117,7 @@ def run_keyword(self, name, args, kwargs=None):
117117
result['error'] = self._get_error_message()
118118
else:
119119
result['status'] = 'PASS'
120-
result['output'] = self._restore_stdout()
120+
result['output'] = self._restore_std_streams()
121121
return result
122122

123123
def _handle_binary_args(self, args, kwargs):
@@ -165,7 +165,7 @@ def _get_keyword(self, name):
165165
def _get_error_details(self):
166166
exc_type, exc_value, exc_tb = sys.exc_info()
167167
if exc_type in self._fatal_exceptions:
168-
self._restore_stdout()
168+
self._restore_std_streams()
169169
raise
170170
return (self._get_error_message(exc_type, exc_value),
171171
self._get_error_traceback(exc_tb))
@@ -228,15 +228,25 @@ def _str(self, item, handle_binary=True):
228228
item = unicode(item)
229229
return self._handle_binary_result(item) if handle_binary else item
230230

231-
def _intercept_stdout(self):
232-
# TODO: What about stderr?
231+
def _intercept_std_streams(self):
233232
sys.stdout = StringIO()
233+
sys.stderr = StringIO()
234234

235-
def _restore_stdout(self):
236-
output = sys.stdout.getvalue()
237-
sys.stdout.close()
235+
def _restore_std_streams(self):
236+
stdout = sys.stdout.getvalue()
237+
stderr = sys.stderr.getvalue()
238+
close = [sys.stdout, sys.stderr]
238239
sys.stdout = sys.__stdout__
239-
return self._handle_binary_result(output)
240+
sys.stderr = sys.__stderr__
241+
for stream in close:
242+
stream.close()
243+
if stdout and stderr:
244+
if not stderr.startswith(('*TRACE*', '*DEBUG*', '*INFO*', '*HTML*',
245+
'*WARN*')):
246+
stderr = '*INFO* %s' % stderr
247+
if not stdout.endswith('\n'):
248+
stdout += '\n'
249+
return self._handle_binary_result(stdout + stderr)
240250

241251
def _log(self, msg, level=None):
242252
if level:

utest/test_robotremoteserver.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env python
22

33
import unittest
4+
import sys
45

56
from robotremoteserver import RobotRemoteServer
67

@@ -14,16 +15,24 @@ def passing_keyword(self):
1415
pass
1516
def failing_keyword(self, exception, message='Hello, world!'):
1617
raise exception(message)
18+
def logging_keyword(self, stdout, stderr):
19+
if stdout:
20+
sys.stdout.write(stdout)
21+
if stderr:
22+
sys.stderr.write(stderr)
23+
self.streams = (sys.stdout, sys.stderr)
1724
def _not_included(self):
1825
"""Starts with an underscore"""
1926
not_included = "Not a method or function"
2027
not_included_2 = NonServingRemoteServer # Callable but not method/function
2128

2229
class HybridLibrary:
30+
def __init__(self):
31+
self.library = StaticLibrary()
2332
def get_keyword_names(self):
24-
return [n for n in dir(StaticLibrary) if n.endswith('_keyword')]
33+
return [n for n in dir(self.library) if n.endswith('_keyword')]
2534
def __getattr__(self, name):
26-
return getattr(StaticLibrary(), name)
35+
return getattr(self.library, name)
2736
def not_included(self):
2837
"""Not returned by get_keyword_names"""
2938

@@ -36,8 +45,8 @@ def setUp(self):
3645

3746
def test_get_keyword_names(self):
3847
self.assertEquals(self.server.get_keyword_names(),
39-
['failing_keyword', 'passing_keyword',
40-
'stop_remote_server'])
48+
['failing_keyword', 'logging_keyword',
49+
'passing_keyword', 'stop_remote_server'])
4150

4251
def test_run_passing_keyword(self):
4352
self.assertEquals(self.server.run_keyword('passing_keyword', []),
@@ -61,6 +70,21 @@ def test_return_only_exception_name_if_no_error_message(self):
6170
self.assertEquals(ret['status'], 'FAIL')
6271
self.assertEquals(ret['error'], exception.__name__)
6372

73+
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))
77+
78+
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))
82+
83+
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')
86+
self.assertTrue(all(s.closed for s in self.library.streams))
87+
6488

6589
class TestHybridApi(TestStaticApi):
6690
library = HybridLibrary()

0 commit comments

Comments
 (0)