Skip to content

Commit f8c1067

Browse files
committed
Initial Python 3 support #17
At the same time also dropped support from Python < 2.6. Docs and setup.py needs to be updated before #17 can be considered done.
1 parent 14cbafe commit f8c1067

19 files changed

+197
-168
lines changed

src/robotremoteserver.py

Lines changed: 51 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
# Copyright 2008-2014 Nokia Solutions and Networks
1+
# Copyright 2008-2015 Nokia Solutions and Networks
2+
# Copyright 2016- Robot Framework Foundation
23
#
34
# Licensed under the Apache License, Version 2.0 (the "License");
45
# you may not use this file except in compliance with the License.
@@ -12,26 +13,32 @@
1213
# See the License for the specific language governing permissions and
1314
# limitations under the License.
1415

15-
__version__ = 'devel'
16+
from __future__ import print_function
1617

18+
from collections import Mapping
1719
import errno
20+
import inspect
1821
import re
22+
import signal
1923
import select
2024
import sys
21-
import inspect
2225
import traceback
23-
from StringIO import StringIO
24-
from SimpleXMLRPCServer import SimpleXMLRPCServer
25-
from xmlrpclib import Binary
26-
try:
27-
import signal
28-
except ImportError:
29-
signal = None
30-
try:
31-
from collections import Mapping
32-
except ImportError:
33-
Mapping = dict
3426

27+
if sys.version_info < (3,):
28+
from StringIO import StringIO
29+
from SimpleXMLRPCServer import SimpleXMLRPCServer
30+
from xmlrpclib import Binary
31+
PY3 = False
32+
else:
33+
from io import StringIO
34+
from xmlrpc.client import Binary
35+
from xmlrpc.server import SimpleXMLRPCServer
36+
PY3 = True
37+
unicode = str
38+
long = int
39+
40+
41+
__version__ = 'devel'
3542

3643
BINARY = re.compile('[\x00-\x08\x0B\x0C\x0E-\x1F]')
3744
NON_ASCII = re.compile('[\x80-\xff]')
@@ -86,11 +93,8 @@ def _announce_start(self, port_file=None):
8693
self._log('Robot Framework remote server at %s:%s starting.'
8794
% (host, port))
8895
if port_file:
89-
pf = open(port_file, 'w')
90-
try:
96+
with open(port_file, 'w') as pf:
9197
pf.write(str(port))
92-
finally:
93-
pf.close()
9498

9599
def serve_forever(self):
96100
if hasattr(self, 'timeout'):
@@ -100,7 +104,7 @@ def serve_forever(self):
100104
while not self._shutdown:
101105
try:
102106
self.handle_request()
103-
except (OSError, select.error), err:
107+
except (OSError, select.error) as err:
104108
if err.args[0] != errno.EINTR:
105109
raise
106110

@@ -114,8 +118,8 @@ def stop_remote_server(self):
114118
return self._shutdown
115119

116120
def get_keyword_names(self):
117-
get_kw_names = getattr(self._library, 'get_keyword_names', None) or \
118-
getattr(self._library, 'getKeywordNames', None)
121+
get_kw_names = (getattr(self._library, 'get_keyword_names', None) or
122+
getattr(self._library, 'getKeywordNames', None))
119123
if self._is_function_or_method(get_kw_names):
120124
names = get_kw_names()
121125
else:
@@ -124,8 +128,6 @@ def get_keyword_names(self):
124128
return names + ['stop_remote_server']
125129

126130
def _is_function_or_method(self, item):
127-
# Cannot use inspect.isroutine because it returns True for
128-
# object().__init__ with Jython and IronPython
129131
return inspect.isfunction(item) or inspect.ismethod(item)
130132

131133
def run_keyword(self, name, args, kwargs=None):
@@ -136,6 +138,9 @@ def run_keyword(self, name, args, kwargs=None):
136138
return_value = self._get_keyword(name)(*args, **kwargs)
137139
except:
138140
exc_type, exc_value, exc_tb = sys.exc_info()
141+
if exc_type in self._fatal_exceptions:
142+
self._restore_std_streams()
143+
raise
139144
self._add_to_result(result, 'error',
140145
self._get_error_message(exc_type, exc_value))
141146
self._add_to_result(result, 'traceback',
@@ -161,7 +166,7 @@ def run_keyword(self, name, args, kwargs=None):
161166

162167
def _handle_binary_args(self, args, kwargs):
163168
args = [self._handle_binary_arg(a) for a in args]
164-
kwargs = dict([(k, self._handle_binary_arg(v)) for k, v in kwargs.items()])
169+
kwargs = dict((k, self._handle_binary_arg(v)) for k, v in kwargs.items())
165170
return args, kwargs
166171

167172
def _handle_binary_arg(self, arg):
@@ -208,9 +213,6 @@ def _get_keyword(self, name):
208213
return kw
209214

210215
def _get_error_message(self, exc_type, exc_value):
211-
if exc_type in self._fatal_exceptions:
212-
self._restore_std_streams()
213-
raise
214216
name = exc_type.__name__
215217
message = self._get_message_from_exception(exc_value)
216218
if not message:
@@ -222,10 +224,11 @@ def _get_error_message(self, exc_type, exc_value):
222224

223225
def _get_message_from_exception(self, value):
224226
# UnicodeError occurs below 2.6 and if message contains non-ASCII bytes
227+
# TODO: Can try/except be removed here?
225228
try:
226229
msg = unicode(value)
227230
except UnicodeError:
228-
msg = ' '.join([self._str(a, handle_binary=False) for a in value.args])
231+
msg = ' '.join(self._str(a, handle_binary=False) for a in value.args)
229232
return self._handle_binary_result(msg)
230233

231234
def _get_error_traceback(self, exc_tb):
@@ -238,13 +241,13 @@ def _get_error_attribute(self, exc_value, name):
238241
return bool(getattr(exc_value, 'ROBOT_%s_ON_FAILURE' % name, False))
239242

240243
def _handle_return_value(self, ret):
241-
if isinstance(ret, basestring):
244+
if isinstance(ret, (str, unicode, bytes)):
242245
return self._handle_binary_result(ret)
243246
if isinstance(ret, (int, long, float)):
244247
return ret
245248
if isinstance(ret, Mapping):
246-
return dict([(self._str(key), self._handle_return_value(value))
247-
for key, value in ret.items()])
249+
return dict((self._str(key), self._handle_return_value(value))
250+
for key, value in ret.items())
248251
try:
249252
return [self._handle_return_value(item) for item in ret]
250253
except TypeError:
@@ -253,23 +256,29 @@ def _handle_return_value(self, ret):
253256
def _handle_binary_result(self, result):
254257
if not self._contains_binary(result):
255258
return result
256-
try:
259+
if not isinstance(result, bytes):
260+
try:
261+
result = result.encode('ASCII')
262+
except UnicodeError:
263+
raise ValueError("Cannot represent %r as binary." % result)
264+
# With IronPython Binary cannot be sent if it contains "real" bytes.
265+
if sys.platform == 'cli':
257266
result = str(result)
258-
except UnicodeError:
259-
raise ValueError("Cannot represent %r as binary." % result)
260267
return Binary(result)
261268

262269
def _contains_binary(self, result):
263-
return (BINARY.search(result) or isinstance(result, str) and
264-
sys.platform != 'cli' and NON_ASCII.search(result))
270+
if PY3:
271+
return isinstance(result, bytes) or BINARY.search(result)
272+
return (isinstance(result, bytes) and NON_ASCII.search(result) or
273+
BINARY.search(result))
265274

266275
def _str(self, item, handle_binary=True):
267276
if item is None:
268277
return ''
269-
if not isinstance(item, basestring):
278+
if not isinstance(item, (str, unicode, bytes)):
270279
item = unicode(item)
271280
if handle_binary:
272-
return self._handle_binary_result(item)
281+
item = self._handle_binary_result(item)
273282
return item
274283

275284
def _intercept_std_streams(self):
@@ -286,7 +295,7 @@ def _restore_std_streams(self):
286295
stream.close()
287296
if stdout and stderr:
288297
if not stderr.startswith(('*TRACE*', '*DEBUG*', '*INFO*', '*HTML*',
289-
'*WARN*')):
298+
'*WARN*', '*ERROR*')):
290299
stderr = '*INFO* %s' % stderr
291300
if not stdout.endswith('\n'):
292301
stdout += '\n'
@@ -310,18 +319,18 @@ def _write_to_stream(self, msg, stream):
310319
def stop(uri):
311320
server = test(uri, log_success=False)
312321
if server is not None:
313-
print 'Stopping remote server at %s.' % uri
322+
print('Stopping remote server at %s.' % uri)
314323
server.stop_remote_server()
315324

316325
def test(uri, log_success=True):
317326
server = xmlrpclib.ServerProxy(uri)
318327
try:
319328
server.get_keyword_names()
320329
except:
321-
print 'No remote server running at %s.' % uri
330+
print('No remote server running at %s.' % uri)
322331
return None
323332
if log_success:
324-
print 'Remote server running at %s.' % uri
333+
print('Remote server running at %s.' % uri)
325334
return server
326335

327336
def parse_args(args):

test/atest/Z_LAST__special_failures.robot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
*** Settings ***
22
Resource resource.robot
3-
Suite Setup Start And Import Remote Library failing.py
3+
Suite Setup Start And Import Remote Library Failing.py
44
Suite Teardown Stop Remote Library
55

66
*** Test Cases ***

test/atest/argument_spec.robot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
*** Settings ***
22
Resource resource.robot
3-
Suite Setup Start And Import Remote Library arguments.py
3+
Suite Setup Start And Import Remote Library Arguments.py
44
Test Template Arguments Should Be Accepted
55
Suite Teardown Stop Remote Library
66

test/atest/argument_spec_invalid.robot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
*** Settings ***
22
Resource resource.robot
3-
Suite Setup Start And Import Remote Library arguments.py
3+
Suite Setup Start And Import Remote Library Arguments.py
44
Suite Teardown Stop Remote Library
55

66
*** Test Cases ***

test/atest/argument_types.robot

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Documentation These tests actually test the Remote library more than the remote server.
33
Resource resource.robot
44
Variables arguments.py
5-
Suite Setup Start And Import Remote Library arguments.py
5+
Suite Setup Start And Import Remote Library Arguments.py
66
Suite Teardown Stop Remote Library
77
Test Template Argument Should Be Correct
88

@@ -18,8 +18,8 @@ Non-ASCII string
1818
u'Hyv\\xe4'
1919
u'\\u2603'
2020

21-
Binary
22-
'\\x00\\x01'
21+
Binary Unicode
22+
'\\x00\\x01' b'\\x00\\x01'
2323

2424
Integer
2525
42
@@ -44,7 +44,7 @@ Custom object
4444
MyObject() '<MyObject>'
4545
MyObject('xxx') 'xxx'
4646
MyObject(u'\\xe4') u'\\xe4'
47-
MyObject('\\x00') '\\x00'
47+
MyObject('\\x00') b'\\x00'
4848

4949
List
5050
\[]
@@ -71,8 +71,7 @@ Dictionary With Non-String Keys
7171
*** Keywords ***
7272
Argument Should Be Correct
7373
[Arguments] ${argument} ${expected}=
74-
${expected} = Set Variable If """${expected}"""
75-
... ${expected} ${argument}
74+
${expected} = Set Variable If $expected ${expected} ${argument}
7675
${ns} = Create Dictionary MyObject=${MyObject}
7776
${argument} = Evaluate ${argument} namespace=${ns}
7877
Remote.Argument Should Be Correct ${argument} ${expected}

test/atest/basic_communication.robot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
*** Settings ***
22
Resource resource.robot
3-
Suite Setup Start And Import Remote Library basics.py
3+
Suite Setup Start And Import Remote Library Basics.py
44
Suite Teardown Stop Remote Library
55

66
*** Variables ***

test/atest/failing.robot

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
*** Settings ***
22
Resource resource.robot
3-
Suite Setup Start And Import Remote Library failing.py
3+
Suite Setup Start And Import Remote Library Failing.py
44
Suite Teardown Stop Remote Library
55
Test Template Correct failure should occur
66

77
*** Variables ***
8-
${SOURCE} File "[\\w: /\\\\]+failing.py", line \\d+
8+
${SOURCE} File "[\\w: /\\\\]+Failing.py", line \\d+
99

1010
*** Test Cases ***
1111
Generic exceptions
@@ -38,16 +38,16 @@ Non-ASCII message
3838
Exception Hyvä \u2603!!
3939

4040
Non-ASCII bytes
41-
Exception 'Hyv\\xe4' Hyv\\xe4 evaluate=yes
41+
Exception b'Hyv\\xe4' *Hyv?* evaluate=yes
4242

43-
Binary message
44-
Exception \x00.\x01.\x02
43+
Binary Unicode
44+
Exception u'\\x00+\\x01+\\x02' \x00+\x01+\x02 evaluate=yes
4545

4646
Non-string message
4747
Exception 42 evaluate=yes
4848
Exception None
4949
Exception ('Message', 42) evaluate=yes
50-
Exception (u'\\xe4 ', 42) evaluate=yes
50+
Exception ('-\\x01-', 42) evaluate=yes
5151

5252
Failure deeper
5353
[Documentation] FAIL Finally failing
@@ -79,7 +79,6 @@ Traceback with multiple entries
7979
*** Keywords ***
8080
Correct failure should occur
8181
[Arguments] ${exception} ${message} ${expected}= ${evaluate}=
82-
${expected} = Set Variable If """${expected}"""
83-
... ${expected} ${message}
82+
${expected} = Set Variable If $expected ${expected} ${message}
8483
Run Keyword And Expect Error ${expected}
8584
... Failure ${exception} ${message} ${evaluate}

test/atest/kwargs.robot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Documentation Cannot combine with other argument test suites because kwargs
33
... cannot be passed to template user keywords they use. This can
44
... be fixed if/when user keywords support kwargs in RF 2.9.
55
Resource resource.robot
6-
Suite Setup Start And Import Remote Library arguments.py
6+
Suite Setup Start And Import Remote Library Arguments.py
77
Suite Teardown Stop Remote Library
88

99
*** Test Cases ***

test/atest/logging.robot

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
*** Settings ***
22
Resource resource.robot
3-
Suite Setup Start And Import Remote Library logging.py
3+
Suite Setup Start And Import Remote Library Logging.py
44
Suite Teardown Stop Remote Library
55

66
*** Test Cases ***
@@ -17,12 +17,13 @@ Non-ASCII message
1717
Logging Hyvä \u2603
1818

1919
Non-ASCII bytes
20-
[Documentation] LOG 1 INFO STARTS: Hyv
21-
Logging 'Hyv\\xe4' evaluate=yes
20+
[Documentation] Different message logged in (py2|py3|ipy).
21+
... LOG 1 INFO REGEXP: (Hyv\\\\xe4|b'Hyv\\\\xe4'|Hyvä)
22+
Logging b'Hyv\\xe4' evaluate=yes
2223

23-
Binary
24-
[Documentation] LOG 1 INFO ..
25-
Logging '\\x00.\\x01.\\x02' evaluate=yes
24+
Binary Unicode
25+
[Documentation] LOG 1 INFO ++
26+
Logging '\\x00+\\x01+\\x02' evaluate=yes
2627

2728
Log levels
2829
[Documentation]

0 commit comments

Comments
 (0)