From 4bfc4425faa16220d072291b829537780a72252e Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 26 Sep 2013 01:38:20 +0200 Subject: [PATCH 001/214] [python3] setup.py: use_2to3 --HG-- extra : transplant_source : %28%1Fj%86%2C%FC%FEO%95R%0Ev%BE%0A%21%88Le%AA%10 --- setup.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 68c8d63204e..a095c8ae7e9 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,8 @@ if 'develop' in sys.argv: import setuptools # support setuptools development mode -execfile(join(dirname(__file__), 'src', 'robot', 'version.py')) +with open(join(dirname(__file__), 'src', 'robot', 'version.py')) as py: + exec(py.read()) # Maximum width in Windows installer seems to be 70 characters -------| DESCRIPTION = """ @@ -34,8 +35,8 @@ 'robot.running.arguments', 'robot.running.timeouts', 'robot.utils', 'robot.variables', 'robot.writer'] PACKAGE_DATA = [join('htmldata', directory, pattern) - for directory in 'rebot', 'libdoc', 'testdoc', 'lib', 'common' - for pattern in '*.html', '*.css', '*.js'] + for directory in ['rebot', 'libdoc', 'testdoc', 'lib', 'common'] + for pattern in ['*.html', '*.css', '*.js']] if sys.platform.startswith('java'): SCRIPTS = ['jybot', 'jyrebot'] elif sys.platform == 'cli': @@ -65,4 +66,9 @@ package_data = {'robot': PACKAGE_DATA}, packages = PACKAGES, scripts = SCRIPTS, + use_2to3 = True, + use_2to3_exclude_fixers = ['lib2to3.fixes.fix_' + fix for fix in [ + 'dict', + 'filter', + ]], ) From 5fb0fde835adb27922418a93a5d212fe86f3dd7b Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 26 Sep 2013 01:57:48 +0200 Subject: [PATCH 002/214] [python3] run_atests: Copy src/robot/ and atest/ and modify for Python 3 before testing --HG-- extra : transplant_source : %99%DC%B0EM%3E%1D9B%23%06%17%D7%2BW%B0K%01%15z --- atest/run_atests.py | 71 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 3 deletions(-) diff --git a/atest/run_atests.py b/atest/run_atests.py index 996e001ac60..8c3e73ef806 100755 --- a/atest/run_atests.py +++ b/atest/run_atests.py @@ -20,6 +20,7 @@ $ atest/run_atests.py /usr/bin/jython25 atest/robot/tags/tag_doc.txt """ +import re import os import shutil import signal @@ -29,7 +30,71 @@ from os.path import abspath, basename, dirname, exists, join, normpath, splitext -CURDIR = dirname(abspath(__file__)) +try: + CURDIR = CURDIR +except NameError: + CURDIR = dirname(abspath(__file__)) +ROBOTDIR = join(CURDIR, '..', 'src', 'robot') + +# If run with Python 3: +# - Copy src/robot/ and atest/ to atest/python3/ +# - Run 2to3 +# - Modify Python literals in Suite/Resource .txt files +# - Exec this file's copy in-place for actual testing +try: + # Is this file already the Python 3 copy or the original? + do2to3 = do2to3 +except NameError: + do2to3 = True +if sys.version_info[0] == 3 and do2to3: + PY3DIR = join(CURDIR, 'python3') + PY3ATESTDIR = join(PY3DIR, 'atest') + + shutil.rmtree((PY3DIR), ignore_errors=True) + os.makedirs(join(PY3DIR, 'src')) + shutil.copytree(ROBOTDIR, join(PY3DIR, 'src', 'robot'), symlinks=True) + shutil.copytree( + CURDIR, join(PY3ATESTDIR), symlinks=True, + ignore=lambda src, names: names if src == PY3DIR else [] + ) + status = subprocess.call( + ['2to3', '--no-diffs', '-n', '-w', + '-x', 'dict', + '-x', 'filter', + PY3DIR + ]) + if status: + sys.exit(status) + + # Modify the Suite/Resource .txt files: + for atest_dirname in ['testdata', 'robot']: + for dirpath, dirnames, filenames in os.walk( + join(PY3ATESTDIR, atest_dirname) + ): + for filename in filenames: + if filename.endswith('.txt'): + path = join(dirpath, filename) + try: + with open(path) as f: + text = f.read() + except UnicodeDecodeError: + pass + else: + print("Preparing for Python 3: %s" % path) + with open(path, 'w') as f: + f.write(re.sub(r'([\[( ])u\'', r'\1\'', text)) + + do2to3 = False + CURDIR = PY3ATESTDIR + + # Redirect the Test Suite arg to the Python3 copy: + sys.argv[-1] = join(CURDIR, sys.argv[-1]) + + # Exec this file's Python 3 copy: + TESTRUNNER = join(CURDIR, 'run_atests.py') + exec(open(TESTRUNNER).read()) + sys.exit(0) + RUNNER = normpath(join(CURDIR, '..', 'src', 'robot', 'run.py')) ARGUMENTS = ' '.join(''' --doc RobotSPFrameworkSPacceptanceSPtests @@ -84,7 +149,7 @@ def atests(interpreter_path, *params): args += ' --noncritical x-fails-on-ipy' command = '%s %s %s %s' % (sys.executable, RUNNER, args, ' '.join(params)) environ = dict(os.environ, TEMPDIR=tempdir) - print 'Running command\n%s\n' % command + print('Running command\n%s\n' % command) sys.stdout.flush() signal.signal(signal.SIGINT, signal.SIG_IGN) return subprocess.call(command.split(), env=environ) @@ -109,7 +174,7 @@ def _get_result_and_temp_dirs(interpreter): if __name__ == '__main__': if len(sys.argv) == 1 or '--help' in sys.argv: - print __doc__ + print(__doc__) rc = 251 else: rc = atests(*sys.argv[1:]) From 2185f7533ff88ff08fa5dc2182c20ae4bc1a1ce9 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 26 Sep 2013 02:02:31 +0200 Subject: [PATCH 003/214] [python3] Compatible UserDict imports --HG-- extra : transplant_source : Q%BFq%7E%2A%BBS%B5%BA%B9-%BD%F5%F3%40%D5%ECM%1A%5C --- .../variables/dynamic_variable_files/dyn_vars.py | 11 +++++++---- src/robot/utils/normalizing.py | 5 ++++- src/robot/variables/variables.py | 5 ++++- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/atest/testdata/variables/dynamic_variable_files/dyn_vars.py b/atest/testdata/variables/dynamic_variable_files/dyn_vars.py index 01cbfac8883..ce0d7746fb5 100644 --- a/atest/testdata/variables/dynamic_variable_files/dyn_vars.py +++ b/atest/testdata/variables/dynamic_variable_files/dyn_vars.py @@ -1,4 +1,7 @@ -import UserDict +try: + from UserDict import UserDict +except ImportError: # Python 3 + from collections import UserDict def get_variables(type): return {'dict': get_dict, @@ -16,11 +19,11 @@ def __init__(self): dict.__init__(self, from_my_dict='This From My Dict', from_my_dict2=2) def get_UserDict(): - userdict = UserDict.UserDict() + userdict = UserDict() userdict.update({'from UserDict': 'This From UserDict', 'from UserDict2': 2}) return userdict -class MyUserDict(UserDict.UserDict): +class MyUserDict(UserDict): def __init__(self, dict): self.data = {} self.update(dict) @@ -34,4 +37,4 @@ def get_JavaMap(): map = HashMap() map.put('from Java Map', 'This From Java Map') map.put('from Java Map2', 2) - return map \ No newline at end of file + return map diff --git a/src/robot/utils/normalizing.py b/src/robot/utils/normalizing.py index b26802a87f7..e966964cfbb 100644 --- a/src/robot/utils/normalizing.py +++ b/src/robot/utils/normalizing.py @@ -14,7 +14,10 @@ import re import sys -from UserDict import UserDict +try: + from collections import UserDict +except ImportError: + from UserDict import UserDict try: from collections import Mapping except ImportError: # Pre Python 2.6 support diff --git a/src/robot/variables/variables.py b/src/robot/variables/variables.py index 14cf168fba6..9cd5032f412 100644 --- a/src/robot/variables/variables.py +++ b/src/robot/variables/variables.py @@ -15,7 +15,10 @@ import re import inspect from functools import partial -from UserDict import UserDict +try: + from collections import UserDict +except ImportError: + from UserDict import UserDict try: from java.lang.System import getProperty as getJavaSystemProperty from java.util import Map From 7ebc2707a97396124a3293795f25d6a7cf34b2f1 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 26 Sep 2013 02:04:29 +0200 Subject: [PATCH 004/214] [python3] Workaround for non-existing exceptions module --HG-- extra : transplant_source : %BA%F5%97%98%CF%96%A9%EE%26%3A%0B%C1Y%FEa%7D%1E%92%3E%7D --- atest/testresources/testlibs/ExampleLibrary.py | 5 ++++- atest/testresources/testlibs/objecttoreturn.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/atest/testresources/testlibs/ExampleLibrary.py b/atest/testresources/testlibs/ExampleLibrary.py index e587d28e409..59b88337cab 100644 --- a/atest/testresources/testlibs/ExampleLibrary.py +++ b/atest/testresources/testlibs/ExampleLibrary.py @@ -1,6 +1,9 @@ import sys import time -import exceptions +try: + import exceptions +except ImportError: # Python 3 + import builtins as exceptions from robot import utils diff --git a/atest/testresources/testlibs/objecttoreturn.py b/atest/testresources/testlibs/objecttoreturn.py index f5ebf3731ff..0a2f1a82e28 100644 --- a/atest/testresources/testlibs/objecttoreturn.py +++ b/atest/testresources/testlibs/objecttoreturn.py @@ -1,4 +1,7 @@ -import exceptions +try: + import exceptions +except ImportError: # Python 3 + import builtins as exceptions class ObjectToReturn: From 79e10639e2d12532f0fb4f68514a244a9978e1ee Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 26 Sep 2013 02:06:32 +0200 Subject: [PATCH 005/214] [python3] robot.errors: Provisional fix for circular import problem --HG-- extra : transplant_source : %1B6%C3%8B%0FL%20%EAN%AF%D6%89%20%01%89%EB%DC%E0v%82 --- src/robot/errors.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/robot/errors.py b/src/robot/errors.py index 309ac40baf8..908b4ab6caf 100644 --- a/src/robot/errors.py +++ b/src/robot/errors.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import utils +##TODO: In Python 3 this causes some circular import problems: +## import utils # Return codes from Robot and Rebot. # RC below 250 is the number of failed critical tests and exactly 250 @@ -80,6 +81,7 @@ def __init__(self, message, 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 . import utils #HACK: See commented global import RobotError.__init__(self, utils.cut_long_message(message)) self.timeout = timeout self.syntax = syntax @@ -118,6 +120,7 @@ def get_errors(self): class HandlerExecutionFailed(ExecutionFailed): def __init__(self): + from . import utils #HACK: See commented global import details = utils.ErrorDetails() timeout = isinstance(details.error, TimeoutError) syntax = isinstance(details.error, DataError) @@ -201,6 +204,7 @@ def __init__(self, message=None, **kwargs): self._earlier_failures = [] def _get_message(self): + from . import utils #HACK: See commented global import return "Invalid '%s' usage." \ % utils.printable_name(self.__class__.__name__, code_style=True) From c2295c0f98a4725aa0b21655d640bdab51957764 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 26 Sep 2013 02:09:30 +0200 Subject: [PATCH 006/214] [python3] jsonwriter: BytesDumper and compatibility fix for StringDumper --HG-- extra : transplant_source : %40%9E%24%D5%B0S%A7%0B%01%CDN%13L%7E%91%2A%8C%DB%5D%A1 --- src/robot/htmldata/jsonwriter.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/robot/htmldata/jsonwriter.py b/src/robot/htmldata/jsonwriter.py index e94a2bcd542..1feef28c4a7 100644 --- a/src/robot/htmldata/jsonwriter.py +++ b/src/robot/htmldata/jsonwriter.py @@ -13,6 +13,9 @@ # limitations under the License. +import sys + + class JsonWriter(object): def __init__(self, output, separator=''): @@ -43,6 +46,7 @@ def __init__(self, output): IntegerDumper(self), TupleListDumper(self), StringDumper(self), + BytesDumper(self), # Only Python 3 NoneDumper(self), DictDumper(self)) @@ -83,8 +87,20 @@ def _encode(self, string): for search, replace in self._search_and_replace: if search in string: string = string.replace(search, replace) + if sys.version_info[0] == 3: + return string return string.encode('UTF-8') +# For Python 3 +class BytesDumper(StringDumper): + try: + _handled_types = bytes + except NameError: + pass + + def _encode(self, string): + return StringDumper._encode(self, string.decode()) + class IntegerDumper(_Dumper): _handled_types = (int, long, bool) From 1298a53382f4c3d9df45f1514b4d5fe71e47066e Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 26 Sep 2013 02:12:26 +0200 Subject: [PATCH 007/214] [python3] Prevent 2to3 from converting some imports --HG-- extra : transplant_source : x%99%08%1D%B6o%7B%17%F6B%3F%96%A0%7Bx%EA%BAk%5E6 --- src/robot/libraries/DeprecatedBuiltIn.py | 4 +++- src/robot/libraries/DeprecatedOperatingSystem.py | 4 +++- src/robot/rebot.py | 4 +++- src/robot/run.py | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/robot/libraries/DeprecatedBuiltIn.py b/src/robot/libraries/DeprecatedBuiltIn.py index 43da453f64f..b626d2eb3cb 100644 --- a/src/robot/libraries/DeprecatedBuiltIn.py +++ b/src/robot/libraries/DeprecatedBuiltIn.py @@ -19,7 +19,9 @@ from robot.utils import asserts -import BuiltIn +## import BuiltIn +#HACK: Prevent 2to3 from converting to relative import +BuiltIn = __import__('BuiltIn') BUILTIN = BuiltIn.BuiltIn() diff --git a/src/robot/libraries/DeprecatedOperatingSystem.py b/src/robot/libraries/DeprecatedOperatingSystem.py index 739262fb2a1..cc4c44dd38c 100644 --- a/src/robot/libraries/DeprecatedOperatingSystem.py +++ b/src/robot/libraries/DeprecatedOperatingSystem.py @@ -13,7 +13,9 @@ # limitations under the License. -import OperatingSystem +## import OperatingSystem +#HACK: Prevent 2to3 from converting to relative import +OperatingSystem = __import__('OperatingSystem') OPSYS = OperatingSystem.OperatingSystem() diff --git a/src/robot/rebot.py b/src/robot/rebot.py index fb6b3846683..a64da21d778 100755 --- a/src/robot/rebot.py +++ b/src/robot/rebot.py @@ -291,7 +291,9 @@ # Allows running as a script. __name__ check needed with multiprocessing: # http://code.google.com/p/robotframework/issues/detail?id=1137 if 'robot' not in sys.modules and __name__ == '__main__': - import pythonpathsetter + ## import pythonpathsetter + #HACK: Prevent 2to3 from converting to relative import + pythonpathsetter = __import__('pythonpathsetter') from robot.conf import RebotSettings from robot.errors import DataError diff --git a/src/robot/run.py b/src/robot/run.py index 3920811a62a..39cfc02b084 100755 --- a/src/robot/run.py +++ b/src/robot/run.py @@ -365,7 +365,9 @@ # Allows running as a script. __name__ check needed with multiprocessing: # http://code.google.com/p/robotframework/issues/detail?id=1137 if 'robot' not in sys.modules and __name__ == '__main__': - import pythonpathsetter + ## import pythonpathsetter + #HACK: Prevent 2to3 from converting to relative import + pythonpathsetter = __import__('pythonpathsetter') from robot.conf import RobotSettings from robot.output import LOGGER From e7600a83ff21b632588c43c6ac9cb3b029465049 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 26 Sep 2013 02:22:33 +0200 Subject: [PATCH 008/214] [python3] stringcache: Compatibility workarounds for long and dict --HG-- extra : transplant_source : %2B%87s%98%9BZ%93%F8%A0%26q%C7%E7.%C6L%91J%D4%3F --- src/robot/reporting/stringcache.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/robot/reporting/stringcache.py b/src/robot/reporting/stringcache.py index 7f7aec13e8e..e9aa7f384a9 100644 --- a/src/robot/reporting/stringcache.py +++ b/src/robot/reporting/stringcache.py @@ -12,11 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys from operator import itemgetter from robot.utils import compress_text +# Normally handled by 2to3, but not for long as base type: +if sys.version_info[0] == 3: + long = int + class StringIndex(long): # Methods below are needed due to http://bugs.jython.org/issue1828 @@ -56,5 +61,5 @@ def _raw(self, text): return '*'+text def dump(self): - return tuple(item[0] for item in sorted(self._cache.iteritems(), + return tuple(item[0] for item in sorted(self._cache.items(), key=itemgetter(1))) From 1e65aa31f047a6972de69987da9266fda994d8d3 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 26 Sep 2013 02:25:13 +0200 Subject: [PATCH 009/214] [python3] robot.utils.text: Use // for compatibility --HG-- extra : transplant_source : %1Bo0%F2%93%EE%1B%97%C4%E2%DD%3F%FA%29%D2%05_k%FFj --- src/robot/utils/text.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/robot/utils/text.py b/src/robot/utils/text.py index 18ee3163ee8..8263cac51c2 100644 --- a/src/robot/utils/text.py +++ b/src/robot/utils/text.py @@ -38,7 +38,8 @@ def _prune_excess_lines(lines, lengths, from_end=False): lengths.reverse() ret = [] total = 0 - limit = _MAX_ERROR_LINES/2 + # Use // (explicit int div) for Python 3 compatibility: + limit = _MAX_ERROR_LINES//2 for line, length in zip(lines[:limit], lengths[:limit]): if total + length >= limit: ret.append(_cut_long_line(line, total, from_end)) @@ -50,7 +51,8 @@ def _prune_excess_lines(lines, lengths, from_end=False): return ret def _cut_long_line(line, used, from_end): - available_lines = _MAX_ERROR_LINES/2 - used + # Use // (explicit int div) for Python 3 compatibility: + available_lines = _MAX_ERROR_LINES//2 - used available_chars = available_lines * _MAX_ERROR_LINE_LENGTH - 3 if len(line) > available_chars: if not from_end: @@ -63,7 +65,8 @@ def _count_line_lenghts(lines): return [ _count_virtual_line_length(line) for line in lines ] def _count_virtual_line_length(line): - length = len(line) / _MAX_ERROR_LINE_LENGTH + # Use // (explicit int div) for Python 3 compatibility: + length = len(line) // _MAX_ERROR_LINE_LENGTH if not len(line) % _MAX_ERROR_LINE_LENGTH == 0 or len(line) == 0: length += 1 return length From e577dda867d0b91390ad30d04c2cba327d47cbf6 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 26 Sep 2013 02:35:06 +0200 Subject: [PATCH 010/214] [python3] Compatibility workarounds for missing unbound methods in Python 3 --HG-- extra : transplant_source : %A8%C7u%EFm%B2%FE%90%DF0%AF%B5%C16s%CE%FDXz%D0 --- src/robot/running/arguments/argumentparser.py | 7 +++++-- src/robot/running/testlibraries.py | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/robot/running/arguments/argumentparser.py b/src/robot/running/arguments/argumentparser.py index 92d5c4454f3..984d1f47b45 100644 --- a/src/robot/running/arguments/argumentparser.py +++ b/src/robot/running/arguments/argumentparser.py @@ -33,7 +33,10 @@ class PythonArgumentParser(_ArgumentParser): def _get_arg_spec(self, handler): args, varargs, kwargs, defaults = inspect.getargspec(handler) - if inspect.ismethod(handler): + # Python 3 has no unbound methods, they are just functions, + # so both tests are needed for compatibility: + #TODO: Some better solution for the second one? + if inspect.ismethod(handler) or (args and args[0] == 'self'): args = args[1:] # drop 'self' defaults = list(defaults) if defaults else [] return args, defaults, varargs, kwargs @@ -133,4 +136,4 @@ def _format_varargs(self, varargs): def _format_arg(self, arg): if not is_scalar_var(arg): raise DataError("Invalid argument '%s'." % arg) - return arg[2:-1] \ No newline at end of file + return arg[2:-1] diff --git a/src/robot/running/testlibraries.py b/src/robot/running/testlibraries.py index 2eedd462888..42dbad1964f 100644 --- a/src/robot/running/testlibraries.py +++ b/src/robot/running/testlibraries.py @@ -125,7 +125,9 @@ def _resolve_init_method(self, libcode): return init_method if self._valid_init(init_method) else lambda: None def _valid_init(self, init_method): - if inspect.ismethod(init_method): + # Python 3 has no unbound methods, they are just functions, + # so both tests are needed for compatibility: + if inspect.isfunction(init_method) or inspect.ismethod(init_method): return True if utils.is_jython and isinstance(init_method, PyReflectedConstructor): return True From 905a98f749d7c4827b663d43bf1156554965086b Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 26 Sep 2013 02:54:44 +0200 Subject: [PATCH 011/214] [python3] BuiltIn: List comprehension vars don't stay in Python 3 --HG-- extra : transplant_source : %E8%EB%DC-j%D8%05F%00%00%27%BDl%28%18a%EDX%EFx --- src/robot/libraries/BuiltIn.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/robot/libraries/BuiltIn.py b/src/robot/libraries/BuiltIn.py index e0ab203e5e0..64f483010d5 100644 --- a/src/robot/libraries/BuiltIn.py +++ b/src/robot/libraries/BuiltIn.py @@ -2516,4 +2516,8 @@ def my_run_keyword_if(self, expression, name, *args): for name in [attr for attr in dir(_RunKeyword) if not attr.startswith('_')]: register_run_keyword('BuiltIn', getattr(_RunKeyword, name)) -del name, attr +try: + del attr +except NameError: # Python 3 + pass +del name From 6ec998858006e35c46ac5fa38780cb537e920e5a Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 29 Sep 2013 01:33:19 +0200 Subject: [PATCH 012/214] [python3] atest: Compatiblity fixes for Python/Jython 2.5 --HG-- extra : transplant_source : %D0%9A%FD%5D%DF%C9%E8%B5%D9L%B20%AD%E2r%D0%F4%C3G/ --- atest/resources/TestHelper.py | 2 ++ atest/resources/read_interpreter.py | 18 +++++++++++++++--- atest/robot/libdoc/LibDocLib.py | 7 ++++++- atest/robot/running/nolog.py | 2 ++ atest/run_atests.py | 1 + 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/atest/resources/TestHelper.py b/atest/resources/TestHelper.py index b9de21ee81e..60e7cb3045c 100644 --- a/atest/resources/TestHelper.py +++ b/atest/resources/TestHelper.py @@ -1,3 +1,5 @@ +from __future__ import with_statement + import os import sys from stat import S_IREAD, S_IWRITE diff --git a/atest/resources/read_interpreter.py b/atest/resources/read_interpreter.py index 8dc24735b31..884dddaac8f 100644 --- a/atest/resources/read_interpreter.py +++ b/atest/resources/read_interpreter.py @@ -1,12 +1,24 @@ import re -from collections import namedtuple +try: + from collections import namedtuple +except ImportError: + pass from robot.utils import ET MATCHER = re.compile(r'.*\((\w*) (.*) on (.*)\)') -Interpreter = namedtuple('Interpreter', ['interpreter', 'version', 'platform']) - +try: + Interpreter = namedtuple('Interpreter', ['interpreter', 'version', 'platform']) +except NameError: + class Interpreter(tuple): + def __new__(cls, *values): + return tuple.__new__(cls, values) + + def __init__(self, interpreter, version, platform): + self.interpreter = interpreter + self.version = version + self.platform = platform def get_interpreter(output): tree = ET.parse(output) diff --git a/atest/robot/libdoc/LibDocLib.py b/atest/robot/libdoc/LibDocLib.py index ceceeab81bd..b160a262e20 100644 --- a/atest/robot/libdoc/LibDocLib.py +++ b/atest/robot/libdoc/LibDocLib.py @@ -1,4 +1,9 @@ -import json +from __future__ import with_statement + +try: + import json +except ImportError: + import simplejson as json import os import pprint import tempfile diff --git a/atest/robot/running/nolog.py b/atest/robot/running/nolog.py index 36efd783369..906e22c33f0 100644 --- a/atest/robot/running/nolog.py +++ b/atest/robot/running/nolog.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +from __future__ import with_statement + def difference_between_stuff(file1, file2): with open(file1) as f1: content1 = f1.readlines() diff --git a/atest/run_atests.py b/atest/run_atests.py index 8c3e73ef806..5b4966a9ea1 100755 --- a/atest/run_atests.py +++ b/atest/run_atests.py @@ -19,6 +19,7 @@ $ atest/run_atests.py python --test example atest/robot $ atest/run_atests.py /usr/bin/jython25 atest/robot/tags/tag_doc.txt """ +from __future__ import with_statement import re import os From 5a76fce4e2a4e810e9f6ad0faab64d60268568ff Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 29 Sep 2013 01:39:20 +0200 Subject: [PATCH 013/214] [python3] Additional workaround for missing unbound methods in Python 3 --HG-- extra : transplant_source : s%CC%EA%EE%F4%E1%D95%23K%A9%06H%96%D6%BFH%3FHt --- src/robot/running/runkwregister.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/robot/running/runkwregister.py b/src/robot/running/runkwregister.py index bfa02886921..f7d8b661fe8 100644 --- a/src/robot/running/runkwregister.py +++ b/src/robot/running/runkwregister.py @@ -39,10 +39,18 @@ def is_run_keyword(self, libname, kwname): return self.get_args_to_process(libname, kwname) >= 0 def _get_args_from_method(self, method): + # Python 3 has no unbound methods, they are just functions, + # so ismethod won't be True... if inspect.ismethod(method): return method.im_func.func_code.co_argcount - 1 elif inspect.isfunction(method): - return method.func_code.co_argcount + code = method.__code__ + argcount = code.co_argcount + # ...but you can look at the args: + #TODO: Better solution? + if argcount and code.co_varnames[0] == 'self': + argcount -= 1 + return argcount raise ValueError('Needs function or method') From 27d4dbbdd17f4988674170b2ba58e5a49dc201f5 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 29 Sep 2013 01:46:34 +0200 Subject: [PATCH 014/214] [python3] Prevent 2to3 from converting some more imports --HG-- extra : transplant_source : j%EA%23%B3%3D%DC%AB%E3n%CB%A8%F5%B78%3D%0D5P%0B%9A --- src/robot/libdoc.py | 3 +++ src/robot/libraries/Dialogs.py | 8 +++++++- src/robot/tidy.py | 14 +++++++++++--- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/robot/libdoc.py b/src/robot/libdoc.py index da85dfea90a..e84b7997476 100755 --- a/src/robot/libdoc.py +++ b/src/robot/libdoc.py @@ -131,6 +131,9 @@ # Allows running as a script. __name__ check needed with multiprocessing: # http://code.google.com/p/robotframework/issues/detail?id=1137 if 'robot' not in sys.modules and __name__ == '__main__': + ## import pythonpathsetter + #HACK: Prevent 2to3 from converting to relative import + pythonpathsetter = __import__('pythonpathsetter') import pythonpathsetter from robot.utils import Application, seq2str diff --git a/src/robot/libraries/Dialogs.py b/src/robot/libraries/Dialogs.py index f5db4d98a7f..91b7bcbd973 100644 --- a/src/robot/libraries/Dialogs.py +++ b/src/robot/libraries/Dialogs.py @@ -35,7 +35,13 @@ elif sys.platform == 'cli': from dialogs_ipy import MessageDialog, PassFailDialog, InputDialog, SelectionDialog else: - from dialogs_py import MessageDialog, PassFailDialog, InputDialog, SelectionDialog + ## from dialogs_py import MessageDialog, PassFailDialog, InputDialog, SelectionDialog + #HACK: Prevent 2to3 from converting to relative import + dialogs_py = __import__('dialogs_py') + MessageDialog = dialogs_py.MessageDialog + PassFailDialog = dialogs_py.PassFailDialog + InputDialog = dialogs_py.InputDialog + SelectionDialog = dialogs_py.SelectionDialog try: from robot.version import get_version diff --git a/src/robot/tidy.py b/src/robot/tidy.py index 8d8fe6174c2..d0144da8bef 100755 --- a/src/robot/tidy.py +++ b/src/robot/tidy.py @@ -115,7 +115,9 @@ # Allows running as a script. __name__ check needed with multiprocessing: # http://code.google.com/p/robotframework/issues/detail?id=1137 if 'robot' not in sys.modules and __name__ == '__main__': - import pythonpathsetter + ## import pythonpathsetter + #HACK: Prevent 2to3 from converting to relative import + pythonpathsetter = __import__('pythonpathsetter') from robot.errors import DataError from robot.parsing import (ResourceFile, TestDataDirectory, TestCaseFile, @@ -147,11 +149,17 @@ def file(self, path, output=None): Use :func:`inplace` to tidy files in-place. """ data = self._parse_data(path) - outfile = open(output, 'wb') if output else StringIO() + mode = 'w' if sys.version_info[0] == 3 else 'wb' + outfile = open(output, mode) if output else StringIO() try: self._save_file(data, outfile) if not output: - return outfile.getvalue().replace('\r\n', '\n').decode('UTF-8') + value = outfile.getvalue().replace('\r\n', '\n') + # Only decode if not already unicode (Python 3 str). + # 2to3 changes `unicode` to `str`. + if type(value) is not unicode: + value = value.decode('UTF-8') + return value finally: outfile.close() From 7f7106d4ad895634f63cd1dcfef4c07e281e01a4 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 29 Sep 2013 01:48:06 +0200 Subject: [PATCH 015/214] [python3] Workaround for UnboundLocalError --HG-- extra : transplant_source : %27%83m7.C%92R%E4G%98%DEL%83%FAW7%AA%15%7E --- src/robot/libraries/BuiltIn.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/robot/libraries/BuiltIn.py b/src/robot/libraries/BuiltIn.py index 64f483010d5..b44b522ee31 100644 --- a/src/robot/libraries/BuiltIn.py +++ b/src/robot/libraries/BuiltIn.py @@ -1332,13 +1332,16 @@ def run_keyword_and_expect_error(self, expected_error, name, *args): except ExecutionFailed, err: if err.dont_continue: raise + # To make err accessible after this except block in Python 3: + # (`err` will be deleted) + exc = err else: raise AssertionError("Expected error '%s' did not occur" % expected_error) - if not self._matches(unicode(err), expected_error): + if not self._matches(unicode(exc), expected_error): raise AssertionError("Expected error '%s' but got '%s'" - % (expected_error, err)) - return unicode(err) + % (expected_error, exc)) + return unicode(exc) def repeat_keyword(self, times, name, *args): """Executes the specified keyword multiple times. From b8503f8efa8f465898d9fff0b26ae70cff47faa4 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 29 Sep 2013 10:30:54 +0200 Subject: [PATCH 016/214] [python3] robot.libraries.dialogs_py: Combatible tkinter import --HG-- extra : transplant_source : %85%D3k%04G%2A%D0%092%E6%1A%F6%17%E5%80%D6%17%AB%D86 --- src/robot/libraries/dialogs_py.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/robot/libraries/dialogs_py.py b/src/robot/libraries/dialogs_py.py index e4bdbbfd9df..58a1b2cc25f 100644 --- a/src/robot/libraries/dialogs_py.py +++ b/src/robot/libraries/dialogs_py.py @@ -14,8 +14,12 @@ import sys from threading import currentThread -from Tkinter import (Tk, Toplevel, Frame, Listbox, Label, Button, Entry, - BOTH, END, LEFT, W) +try: + from Tkinter import (Tk, Toplevel, Frame, Listbox, Label, Button, Entry, + BOTH, END, LEFT, W) +except ImportError: # Python 3 + from tkinter import (Tk, Toplevel, Frame, Listbox, Label, Button, Entry, + BOTH, END, LEFT, W) class _TkDialog(Toplevel): From 7ef4c19fe36aab91725deb5c96a36014a23ce976 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 29 Sep 2013 10:34:18 +0200 Subject: [PATCH 017/214] [python3] Accompanying __lt__ methods for __cmp__ --HG-- extra : transplant_source : S%00%E5%81%02%E7%23%C6%F4z%0D%00%09%D5%AFAxb%3B%F9 --- src/robot/model/stats.py | 19 +++++++++++++++++++ src/robot/running/timeouts/__init__.py | 9 +++++++++ 2 files changed, 28 insertions(+) diff --git a/src/robot/model/stats.py b/src/robot/model/stats.py index 2adbbaa5f48..7a3a12a3d15 100644 --- a/src/robot/model/stats.py +++ b/src/robot/model/stats.py @@ -81,6 +81,14 @@ def _update_elapsed(self, test): def __cmp__(self, other): return cmp(self._norm_name, other._norm_name) + def __lt__(self, other): + return self._norm_name < other._norm_name + + #TODO: Necessary? Are Stats ever compared with other than < ? + ## def __eq__(self, other): + ## ... + ## return self._norm_name == other._norm_name + def __nonzero__(self): return not self.failed @@ -167,6 +175,17 @@ def __cmp__(self, other): or cmp(bool(other.combined), bool(self.combined)) \ or Stat.__cmp__(self, other) + def __lt__(self, other): + key = (other.critical, other.non_critical, other.combined, + self._norm_name) + other_key = (self.critical, self.non_critical, self.combined, + other._norm_name) + return key < other_key + + #TODO: Necessary? See commented Stat.__eq__ + ## def __eq__(self, other): + ## ... + class CombinedTagStat(TagStat): diff --git a/src/robot/running/timeouts/__init__.py b/src/robot/running/timeouts/__init__.py index d9eac2150fe..2a4c2395213 100644 --- a/src/robot/running/timeouts/__init__.py +++ b/src/robot/running/timeouts/__init__.py @@ -85,6 +85,15 @@ def __cmp__(self, other): return cmp(not self.active, not other.active) \ or cmp(self.time_left(), other.time_left()) + def __lt__(self, other): + key = (not self.active, self.time_left()) + other_key = (not other.active, other.time_left()) + return key < other_key + + #TODO: Necessary? Are _Timeouts ever compared with other than < ? + ## def __eq__(self, other): + ## ... + def __nonzero__(self): return bool(self.string and self.string.upper() != 'NONE') From 06211aa0dcac96dc67067c231a2c1f5debb2c27d Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 29 Sep 2013 10:36:37 +0200 Subject: [PATCH 018/214] [python3] robot.running.namespace: Use itertools.chain to combine sequences --HG-- extra : transplant_source : %0B%13I%87%C5%7FW%FBI%96%ECj%F79%2A%0F%03o%EF%CB --- src/robot/running/namespace.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/robot/running/namespace.py b/src/robot/running/namespace.py index 57c398193dd..49ddeb811fc 100644 --- a/src/robot/running/namespace.py +++ b/src/robot/running/namespace.py @@ -15,6 +15,7 @@ import os import sys import copy +from itertools import chain from robot import utils from robot.errors import DataError @@ -359,7 +360,7 @@ def _get_explicit_handler(self, name): libname, kwname = name.rsplit('.', 1) # 1) Find matching lib(s) libs = [lib for lib - in self._imported_resource_files.values() + self._testlibs.values() + in chain(self._imported_resource_files.values(), self._testlibs.values()) if utils.eq(lib.name, libname)] if not libs: return None From 11a1b90e19f681731a13c573e4d84ebd958a21b7 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 29 Sep 2013 11:04:29 +0200 Subject: [PATCH 019/214] [python3] Many workarounds for bytes/str related issues --HG-- extra : transplant_source : %82%9Ak%A4%01%85%AD%ABm%9E%9F%89U%B0%DD%B4%AC9%8F%A0 --- src/robot/libraries/OperatingSystem.py | 2 ++ src/robot/model/itemlist.py | 5 +++++ src/robot/model/message.py | 3 +++ src/robot/model/metadata.py | 4 ++++ src/robot/model/modelobject.py | 4 ++++ src/robot/model/tags.py | 4 ++++ src/robot/output/debugfile.py | 7 ++++++- src/robot/output/filelogger.py | 6 +++++- src/robot/parsing/htmlreader.py | 14 ++++++++++++-- src/robot/parsing/settings.py | 2 ++ src/robot/parsing/tsvreader.py | 5 ++++- src/robot/running/timeouts/__init__.py | 2 ++ src/robot/utils/argumentparser.py | 6 +++++- src/robot/utils/encoding.py | 2 +- src/robot/utils/markupwriters.py | 6 +++++- src/robot/utils/unic.py | 6 ++++++ src/robot/writer/datafilewriter.py | 6 +++++- src/robot/writer/filewriters.py | 19 ++++++++++++++++--- 18 files changed, 91 insertions(+), 12 deletions(-) diff --git a/src/robot/libraries/OperatingSystem.py b/src/robot/libraries/OperatingSystem.py index ee9b80654d6..3bf00a7a98c 100644 --- a/src/robot/libraries/OperatingSystem.py +++ b/src/robot/libraries/OperatingSystem.py @@ -1286,6 +1286,8 @@ def _process_command(self, command): command = command[:-1] + ' 2>&1 &' else: command += ' 2>&1' + if sys.version_info[0] == 3: + return command return self._encode_to_file_system(command) def _encode_to_file_system(self, string): diff --git a/src/robot/model/itemlist.py b/src/robot/model/itemlist.py index 76196bf4650..975914463bb 100644 --- a/src/robot/model/itemlist.py +++ b/src/robot/model/itemlist.py @@ -13,6 +13,9 @@ # limitations under the License. +import sys + + class ItemList(object): __slots__ = ['_item_class', '_common_attrs', '_items'] @@ -74,4 +77,6 @@ def __unicode__(self): return u'[%s]' % ', '.join(unicode(item) for item in self) def __str__(self): + if sys.version_info[0] == 3: + return self.__unicode__() return unicode(self).encode('ASCII', 'replace') diff --git a/src/robot/model/message.py b/src/robot/model/message.py index 735ab49c668..09a8d5e40f2 100644 --- a/src/robot/model/message.py +++ b/src/robot/model/message.py @@ -51,6 +51,9 @@ def visit(self, visitor): def __unicode__(self): return self.message + def __str__(self): + return self.message + class Messages(ItemList): __slots__ = [] diff --git a/src/robot/model/metadata.py b/src/robot/model/metadata.py index ec2de8a1a9d..aaeb568f2e8 100644 --- a/src/robot/model/metadata.py +++ b/src/robot/model/metadata.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys + from robot.utils import NormalizedDict @@ -24,4 +26,6 @@ def __unicode__(self): return u'{%s}' % ', '.join('%s: %s' % (k, self[k]) for k in self) def __str__(self): + if sys.version_info[0] == 3: + return self.__unicode__() return unicode(self).encode('ASCII', 'replace') diff --git a/src/robot/model/modelobject.py b/src/robot/model/modelobject.py index 2b22001feaa..54ef060a202 100644 --- a/src/robot/model/modelobject.py +++ b/src/robot/model/modelobject.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys + from robot.utils.setter import SetterAwareType @@ -23,6 +25,8 @@ def __unicode__(self): return self.name def __str__(self): + if sys.version_info[0] == 3: + return self.__unicode__() return unicode(self).encode('ASCII', 'replace') def __repr__(self): diff --git a/src/robot/model/tags.py b/src/robot/model/tags.py index 3f50fd57fa2..bd3acfbd6a9 100644 --- a/src/robot/model/tags.py +++ b/src/robot/model/tags.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys + from robot.utils import Matcher, NormalizedDict, setter @@ -61,6 +63,8 @@ def __repr__(self): return repr(list(self)) def __str__(self): + if sys.version_info[0] == 3: + return self.__unicode__() return unicode(self).encode('UTF-8') def __getitem__(self, index): diff --git a/src/robot/output/debugfile.py b/src/robot/output/debugfile.py index 63b36f3ccae..9eb6768f68a 100644 --- a/src/robot/output/debugfile.py +++ b/src/robot/output/debugfile.py @@ -103,6 +103,11 @@ def _separator(self, type_): def _write(self, text, separator=False): if not (separator and self._separator_written_last): - self._outfile.write(text.encode('UTF-8').rstrip() + '\n') + text = text.rstrip() + '\n' + encoded_text = text.encode('UTF-8') + try: + self._outfile.write(encoded_text) + except TypeError: # Python 3 + self._outfile.write(text) self._outfile.flush() self._separator_written_last = separator diff --git a/src/robot/output/filelogger.py b/src/robot/output/filelogger.py index 2c6ca1ec0b4..3fd6fbd7d02 100644 --- a/src/robot/output/filelogger.py +++ b/src/robot/output/filelogger.py @@ -33,7 +33,11 @@ def message(self, msg): if self._is_logged(msg.level) and not self._writer.closed: entry = '%s | %s | %s\n' % (msg.timestamp, msg.level.ljust(5), msg.message) - self._writer.write(entry.encode('UTF-8')) + encoded_entry = entry.encode('UTF-8') + try: + self._writer.write(encoded_entry) + except TypeError: # Python 3 + self._writer.write(entry) def start_suite(self, suite): self.info("Started test suite '%s'" % suite.name) diff --git a/src/robot/parsing/htmlreader.py b/src/robot/parsing/htmlreader.py index a5f407c6c5f..b58a902d97a 100644 --- a/src/robot/parsing/htmlreader.py +++ b/src/robot/parsing/htmlreader.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys + from HTMLParser import HTMLParser from htmlentitydefs import entitydefs @@ -44,7 +46,11 @@ def read(self, htmlfile, populator): self.current_row = None self.current_cell = None for line in htmlfile.readlines(): - self.feed(self._decode(line)) + # Only decode if not already unicode (Python 3 str). + # 2to3 changes `unicode` to `str`. + if type(line) is not unicode: + line = self._decode(line) + self.feed(line) # Calling close is required by the HTMLParser but may cause problems # if the same instance of our HtmlParser is reused. Currently it's # used only once so there's no problem. @@ -84,7 +90,11 @@ def _handle_entityref(self, name): return '&'+name+';' if value.startswith('&#'): return unichr(int(value[2:-1])) - return value.decode('ISO-8859-1') + # Only decode if not already unicode (Python 3 str). + # 2to3 changes `unicode` to `str`. + if type(value) is not unicode: + value = value.decode('ISO-8859-1') + return value def handle_charref(self, number): value = self._handle_charref(number) diff --git a/src/robot/parsing/settings.py b/src/robot/parsing/settings.py index 30e652f0cbb..cafdc6fee7f 100644 --- a/src/robot/parsing/settings.py +++ b/src/robot/parsing/settings.py @@ -83,6 +83,8 @@ def __iter__(self): def __unicode__(self): return unicode(self.value or '') + def __str__(self): + return self.__unicode__() class StringValueJoiner(object): diff --git a/src/robot/parsing/tsvreader.py b/src/robot/parsing/tsvreader.py index ecd98d3a455..b5571c337fb 100644 --- a/src/robot/parsing/tsvreader.py +++ b/src/robot/parsing/tsvreader.py @@ -23,7 +23,10 @@ class TsvReader: def read(self, tsvfile, populator): process = False for index, row in enumerate(tsvfile.readlines()): - row = self._decode_row(row, index == 0) + # Only decode if not already unicode (Python 3 str). + # 2to3 changes `unicode` to `str`. + if type(row) is not unicode: + row = self._decode_row(row, index == 0) cells = [self._process(cell) for cell in self.split_row(row)] name = cells and cells[0].strip() or '' if name.startswith('*') and \ diff --git a/src/robot/running/timeouts/__init__.py b/src/robot/running/timeouts/__init__.py index 2a4c2395213..1447ace05f9 100644 --- a/src/robot/running/timeouts/__init__.py +++ b/src/robot/running/timeouts/__init__.py @@ -76,6 +76,8 @@ def timed_out(self): return self.active and self.time_left() <= 0 def __str__(self): + if sys.version_info[0] == 3: + return self.__unicode__() return unicode(self).encode('utf-8') def __unicode__(self): diff --git a/src/robot/utils/argumentparser.py b/src/robot/utils/argumentparser.py index 8c1a8381a28..d9e9b30493b 100644 --- a/src/robot/utils/argumentparser.py +++ b/src/robot/utils/argumentparser.py @@ -395,7 +395,11 @@ def _get_args(self, path): def _read_from_file(self, path): try: with open(path) as f: - content = f.read().decode('UTF-8') + content = f.read() + # Only decode if not already unicode (Python 3 str). + # 2to3 changes `unicode` to `str`. + if type(content) is not unicode: + content = content.decode('UTF-8') except (IOError, UnicodeError), err: raise DataError("Opening argument file '%s' failed: %s" % (path, err)) diff --git a/src/robot/utils/encoding.py b/src/robot/utils/encoding.py index 679981ba3df..96c3d317945 100644 --- a/src/robot/utils/encoding.py +++ b/src/robot/utils/encoding.py @@ -30,7 +30,7 @@ def decode_output(string): def encode_output(string, errors='replace'): """Encodes Unicode to bytes in console encoding.""" # http://ironpython.codeplex.com/workitem/29487 - if sys.platform == 'cli': + if sys.version_info[0] == 3 or sys.platform == 'cli': return string return string.encode(OUTPUT_ENCODING, errors) diff --git a/src/robot/utils/markupwriters.py b/src/robot/utils/markupwriters.py index 3912ca019a5..a0134c17954 100644 --- a/src/robot/utils/markupwriters.py +++ b/src/robot/utils/markupwriters.py @@ -71,7 +71,11 @@ def close(self): self.output.close() def _write(self, text, newline=False): - self.output.write(self._encode(text)) + encoded_text = self._encode(text) + try: + self.output.write(encoded_text) + except TypeError: # Python 3 + self.output.write(text) if newline: self.output.write(self._line_separator) diff --git a/src/robot/utils/unic.py b/src/robot/utils/unic.py index f2b4e642931..4774f36b377 100644 --- a/src/robot/utils/unic.py +++ b/src/robot/utils/unic.py @@ -44,6 +44,12 @@ def unic(item, *args): def _unic(item, *args): + # First check if already unicode (Python 3 str) + # --> Python 3 will raise TypeError + # if trying to decode with str(item, *args) below. + # 2to3 changes `unicode` to `str`. + if type(item) is unicode: + return item # Based on a recipe from http://code.activestate.com/recipes/466341 try: return unicode(item, *args) diff --git a/src/robot/writer/datafilewriter.py b/src/robot/writer/datafilewriter.py index 0317f676a1a..e818198402c 100644 --- a/src/robot/writer/datafilewriter.py +++ b/src/robot/writer/datafilewriter.py @@ -14,6 +14,7 @@ from __future__ import with_statement import os +import sys from robot.errors import DataError @@ -94,7 +95,10 @@ def __init__(self, datafile, format='', output=None, pipe_separated=False, def __enter__(self): if not self.output: - self.output = open(self._output_path(), 'wb') + # In Python 3, open with 'wb' only accepts bytes data, + # which causes TypeErrors at other points + mode = 'w' if sys.version_info[0] == 3 else 'wb' + self.output = open(self._output_path(), mode) return self def __exit__(self, *exc_info): diff --git a/src/robot/writer/filewriters.py b/src/robot/writer/filewriters.py index 1c16418a4c2..9a160f50ccc 100644 --- a/src/robot/writer/filewriters.py +++ b/src/robot/writer/filewriters.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys try: import csv except ImportError: @@ -86,7 +87,11 @@ def __init__(self, configuration): def _write_row(self, row): line = self._separator.join(row).rstrip() + self._line_separator - self._output.write(self._encode(line)) + encoded_line = self._encode(line) + try: + self._output.write(encoded_line) + except TypeError: # Python 3 + self._output.write(line) class PipeSeparatedTxtWriter(_DataFileWriter): @@ -100,7 +105,12 @@ def _write_row(self, row): row = self._separator.join(row) if row: row = '| ' + row + ' |' - self._output.write(self._encode(row + self._line_separator)) + row += row + self._line_separator + encoded_row = self._encode(row) + try: + self._output.write(encoded_row) + except TypeError: # Python 3 + self._output.write(row) class TsvFileWriter(_DataFileWriter): @@ -121,7 +131,10 @@ def _get_writer(self, configuration): return csv.writer(configuration.output, dialect=dialect) def _write_row(self, row): - self._writer.writerow([self._encode(c) for c in row]) + if sys.version_info[0] == 3: + self._writer.writerow(list(row)) + else: + self._writer.writerow([self._encode(c) for c in row]) class HtmlFileWriter(_DataFileWriter): From cd174e02553e56cfbf89433a98a15ed10507259c Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 29 Sep 2013 13:00:06 +0200 Subject: [PATCH 020/214] [python3] Additional __lt__ method --HG-- extra : transplant_source : %E44V%B6%11%15%AA%F4%80%CC%D7%CA%BB%04%D8%82wP%E7%03 --- src/robot/libdocpkg/model.py | 7 +++++++ src/robot/model/stats.py | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/robot/libdocpkg/model.py b/src/robot/libdocpkg/model.py index dd76fb81058..58b6b0d8964 100644 --- a/src/robot/libdocpkg/model.py +++ b/src/robot/libdocpkg/model.py @@ -66,3 +66,10 @@ def shortdoc(self): def __cmp__(self, other): return cmp(self.name.lower(), other.name.lower()) + + def __lt__(self, other): + return self.name.lower() < other.name.lower() + + #TODO: Necessary? Are KeywordDocs ever compared with other than < ? + ## def __eq__(self, other): + ## ... diff --git a/src/robot/model/stats.py b/src/robot/model/stats.py index 7a3a12a3d15..636d6d9c02b 100644 --- a/src/robot/model/stats.py +++ b/src/robot/model/stats.py @@ -87,7 +87,6 @@ def __lt__(self, other): #TODO: Necessary? Are Stats ever compared with other than < ? ## def __eq__(self, other): ## ... - ## return self._norm_name == other._norm_name def __nonzero__(self): return not self.failed From 17afba1b339879d0ea9e2d92184683b8bf14da57 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 29 Sep 2013 13:12:01 +0200 Subject: [PATCH 021/214] [python3] atest_resource: Run on python 2.x/3.x Keywords and little bytes/str issue workaround --HG-- extra : transplant_source : %AB%3E%CFa%ABc%3E%94%EEK%84%26Qe%5D%B0%EB%A9%D8%BF --- atest/resources/atest_resource.txt | 14 +++++++++++++- atest/resources/read_interpreter.py | 6 ++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/atest/resources/atest_resource.txt b/atest/resources/atest_resource.txt index 9e5b4136c57..7479b197348 100644 --- a/atest/resources/atest_resource.txt +++ b/atest/resources/atest_resource.txt @@ -87,7 +87,7 @@ Set Variables And Get Datasources Set Variables [Arguments] ${name} ${OUTDIR} = Join Path ${OUTPUTDIR} output ${name} - Set Global Variable $OUTDIR ${OUTDIR.encode('ascii', 'ignore').replace('?', '_') .replace('*', '_')} + Set Global Variable $OUTDIR ${OUTDIR.encode('ascii', 'ignore').decode().replace('?', '_') .replace('*', '_')} Create Directory ${OUTDIR} Set Suite Variable $OUTFILE ${OUTDIR}${/}output.xml Set Suite Variable $STDOUT_FILE ${OUTDIR}${/}stdout.txt @@ -332,3 +332,15 @@ Run on python 2.5 ${interpreter} = Get interpreter ${OUTFILE} ${is 25} = is 25 ${interpreter} Run keyword if ${is 25} ${kw} @{args} + +Run on python 3.x + [arguments] ${kw} @{args} + ${interpreter} = Get interpreter ${OUTFILE} + ${is 3x} = is 3x ${interpreter} + Run keyword if ${is 3x} ${kw} @{args} + +Run on python 2.x + [arguments] ${kw} @{args} + ${interpreter} = Get interpreter ${OUTFILE} + ${is 2x} = is 2x ${interpreter} + Run keyword if ${is 2x} ${kw} @{args} diff --git a/atest/resources/read_interpreter.py b/atest/resources/read_interpreter.py index 884dddaac8f..9c00af107fe 100644 --- a/atest/resources/read_interpreter.py +++ b/atest/resources/read_interpreter.py @@ -25,6 +25,12 @@ def get_interpreter(output): root = tree.getroot() return Interpreter(*MATCHER.match(root.attrib['generator']).groups()) +def is_3x(interpreter): + return interpreter.version.startswith('3') + +def is_2x(interpreter): + return interpreter.version.startswith('2') + def is_27(interpreter): return interpreter.version.startswith('2.7') From 168fa35efc017114ab36856ed37dd1ef5778b389 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 29 Sep 2013 13:15:43 +0200 Subject: [PATCH 022/214] [python3] atest: Extended robot version regex to support Python 3 --HG-- extra : transplant_source : %DB%AD%7D%C3%D1%26%B5a%23r%B8ms2%AD%1E%279%E5%9F --- atest/robot/cli/rebot/help_and_version.txt | 2 +- atest/robot/cli/runner/help_and_version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/atest/robot/cli/rebot/help_and_version.txt b/atest/robot/cli/rebot/help_and_version.txt index 1ff4d2132ae..f88f16886da 100644 --- a/atest/robot/cli/rebot/help_and_version.txt +++ b/atest/robot/cli/rebot/help_and_version.txt @@ -26,5 +26,5 @@ Version ${rc} ${output} = Run And Return Rc And Output ${REBOT} --version 2>&1 Should Be Equal ${rc} ${251} Log ${output} - Should Match Regexp ${output} ^Rebot (2\\.\\d+(\\.\\d+)?( (a|b|c)\\d*)?|trunk 20\\d{6}) \\((Python|Jython|IronPython) 2\\.[\\d.]+.* on .+\\)$ + Should Match Regexp ${output} ^Rebot (2\\.\\d+(\\.\\d+)?( (a|b|c)\\d*)?|trunk 20\\d{6}) \\((Python|Jython|IronPython) [23]\\.[\\d.]+.* on .+\\)$ Should Be True len("${output}") < 80 Too long version line diff --git a/atest/robot/cli/runner/help_and_version.txt b/atest/robot/cli/runner/help_and_version.txt index 3d5d0c29c6c..81fb46dcd81 100644 --- a/atest/robot/cli/runner/help_and_version.txt +++ b/atest/robot/cli/runner/help_and_version.txt @@ -27,5 +27,5 @@ Version ${rc} ${output} = Run And Return Rc And Output ${ROBOT} --version Should Be Equal ${rc} ${251} Log ${output} - Should Match Regexp ${output} ^Robot Framework (2\\.\\d+(\\.\\d+)?( (a|b|c)\\d*)?|trunk 20\\d{6}) \\((Python|Jython|IronPython) 2\\.[\\d.]+.* on .+\\)$ + Should Match Regexp ${output} ^Robot Framework (2\\.\\d+(\\.\\d+)?( (a|b|c)\\d*)?|trunk 20\\d{6}) \\((Python|Jython|IronPython) [23]\\.[\\d.]+.* on .+\\)$ Should Be True len("${output}") < 80 Too long version line From 59b39a448498d7b1ef5654718485aeab60feb321 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 29 Sep 2013 14:01:26 +0200 Subject: [PATCH 023/214] [python3] atest: Workarounds for bytes/str related issues --HG-- extra : transplant_source : L%3C%1B%7D%FBy%D1%7B%B3YI%F0%2C%FA%9A%AD%0A8G%28 --- atest/robot/cli/runner/syslog.txt | 2 +- atest/robot/libdoc/LibDocLib.py | 5 ++++- atest/robot/running/ProcessManager.py | 7 ++++++- .../standard_libraries/operating_system/create_file.txt | 5 ++++- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/atest/robot/cli/runner/syslog.txt b/atest/robot/cli/runner/syslog.txt index 3ef8666d7cb..9be3dc7316a 100644 --- a/atest/robot/cli/runner/syslog.txt +++ b/atest/robot/cli/runner/syslog.txt @@ -18,7 +18,7 @@ Setting syslog sile Run Some Tests File Should Not Be Empty ${CLI OUTDIR}/syslog.txt ${syslog} = Get Binary File ${CLI OUTDIR}/syslog.txt - ${linesep} = Evaluate os.linesep modules=os + ${linesep} = Evaluate os.linesep.encode() modules=os Should Contain ${syslog} ${linesep} Syslog file set to NONE diff --git a/atest/robot/libdoc/LibDocLib.py b/atest/robot/libdoc/LibDocLib.py index b160a262e20..7272ba1bf99 100644 --- a/atest/robot/libdoc/LibDocLib.py +++ b/atest/robot/libdoc/LibDocLib.py @@ -25,11 +25,14 @@ def run_libdoc(self, args): cmd = self._cmd + [a for a in args.split(' ') if a] cmd[-1] = cmd[-1].replace('/', os.sep) logger.info(' '.join(cmd)) - stdout = tempfile.TemporaryFile() + # In Python 3, explicitly open in text mode (w+, default w+b) + # causes less problems (works with str, not bytes): + stdout = tempfile.TemporaryFile('w+') call(cmd, cwd=ROBOT_SRC, stdout=stdout, stderr=STDOUT, shell=os.sep=='\\') stdout.seek(0) output = stdout.read().replace('\r\n', '\n') logger.info(output) + # Python 3 compatibility is handled by robot.utils.unic: return decode_output(output) def get_libdoc_model_from_html(self, path): diff --git a/atest/robot/running/ProcessManager.py b/atest/robot/running/ProcessManager.py index 553267f67dc..b56328f9093 100644 --- a/atest/robot/running/ProcessManager.py +++ b/atest/robot/running/ProcessManager.py @@ -13,7 +13,12 @@ def __init__(self): def start_process(self, *args): self._process = subprocess.Popen(args, stderr=subprocess.PIPE, - stdout=subprocess.PIPE) + stdout=subprocess.PIPE, + # Important for Python 3: + # (opens stdout and stderr + # in text mode, returning str + # instead of bytes) + universal_newlines=True) self._stdout = None self._stderr = None diff --git a/atest/testdata/standard_libraries/operating_system/create_file.txt b/atest/testdata/standard_libraries/operating_system/create_file.txt index ada944153f0..deaeaa0ee23 100644 --- a/atest/testdata/standard_libraries/operating_system/create_file.txt +++ b/atest/testdata/standard_libraries/operating_system/create_file.txt @@ -43,7 +43,10 @@ Create File To Non-Existing Dir *** Keywords *** Create Non-ASCII Input ${BYTES} = Evaluate '\xc1\xdf\xd0\xe1\xd8\xd1\xde' - ${UNICODE} = Evaluate '${BYTES}'.decode('ISO-8859-5') + ${status} ${UNICODE} = Run Keyword And Ignore Error + ... Evaluate '${BYTES}'.decode('ISO-8859-5') + ${status} ${UNICODE} = Run Keyword And Ignore Error + ... Evaluate b'${BYTES}'.decode('ISO-8859-5') Set Suite Variable ${BYTES} Set Suite Variable ${UNICODE} From 1800c7896b5e7a1e28b152d58e4f97106f41c986 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 29 Sep 2013 14:04:02 +0200 Subject: [PATCH 024/214] [python3] atest: Some Run on python 2.x/3.x switches for checking exception messages --HG-- extra : transplant_source : %A1%12%C6%E2%CE%FFw%12%5B%1C%A2%1B%B8%E0%95%A7H%BC%A5%C0 --- atest/robot/cli/dryrun/dryrun.txt | 5 ++++- .../cli/runner/deprecated_runmode/dryrun.txt | 5 ++++- .../listener_interface/importing_listeners.txt | 12 ++++++++++-- .../old_importing_listeners.txt | 11 +++++++++-- .../test_libraries/error_msg_and_details.txt | 15 ++++++++++++--- .../test_libraries/library_imports_by_path.txt | 10 ++++++++-- 6 files changed, 47 insertions(+), 11 deletions(-) diff --git a/atest/robot/cli/dryrun/dryrun.txt b/atest/robot/cli/dryrun/dryrun.txt index f8ef7a96146..d1c4bc26eb6 100644 --- a/atest/robot/cli/dryrun/dryrun.txt +++ b/atest/robot/cli/dryrun/dryrun.txt @@ -90,7 +90,10 @@ Multiple Failures Check Test Case ${TESTNAME} Invalid imports - Check Stderr Contains Importing test library 'DoesNotExist' failed: ImportError: No module named DoesNotExist + Run on python 3.x + ... Check Stderr Contains Importing test library 'DoesNotExist' failed: ImportError: No module named 'DoesNotExist' + Run on python 2.x + ... Check Stderr Contains Importing test library 'DoesNotExist' failed: ImportError: No module named DoesNotExist Check Stderr Contains Variable file 'wrong_path.py' does not exist Check Stderr Contains Resource file 'NonExisting.tsv' does not exist diff --git a/atest/robot/cli/runner/deprecated_runmode/dryrun.txt b/atest/robot/cli/runner/deprecated_runmode/dryrun.txt index 78fdc0a5a2f..39a3790e634 100644 --- a/atest/robot/cli/runner/deprecated_runmode/dryrun.txt +++ b/atest/robot/cli/runner/deprecated_runmode/dryrun.txt @@ -90,7 +90,10 @@ Multiple Failures Check Test Case ${TESTNAME} Invalid imports - Check Stderr Contains Importing test library 'DoesNotExist' failed: ImportError: No module named DoesNotExist + Run on python 3.x + ... Check Stderr Contains Importing test library 'DoesNotExist' failed: ImportError: No module named 'DoesNotExist' + Run on python 2.x + ... Check Stderr Contains Importing test library 'DoesNotExist' failed: ImportError: No module named DoesNotExist Check Stderr Contains Variable file 'wrong_path.py' does not exist Check Stderr Contains Resource file 'NonExisting.tsv' does not exist diff --git a/atest/robot/output/listener_interface/importing_listeners.txt b/atest/robot/output/listener_interface/importing_listeners.txt index b59e9f9bf76..f133e76f080 100644 --- a/atest/robot/output/listener_interface/importing_listeners.txt +++ b/atest/robot/output/listener_interface/importing_listeners.txt @@ -35,11 +35,19 @@ Listener With Wrong Number Of Arguments ... Creating instance failed: TypeError: Non Existing Listener - [Template] Check Syslog Contains - Taking listener 'NonExistingListener' into use failed: + [Template] Run on python 2.x + Check Syslog Contains + ... Taking listener 'NonExistingListener' into use failed: ... Importing listener 'NonExistingListener' failed: ... ImportError: No module named NonExistingListener${EMPTY TB} +Non Existing Listener Python 3 + [Template] Run on python 3.x + Check Syslog Contains + ... Taking listener 'NonExistingListener' into use failed: + ... Importing listener 'NonExistingListener' failed: + ... ImportError: No module named 'NonExistingListener'${EMPTY TB} + Java Listener [Tags] jybot class JavaListener diff --git a/atest/robot/output/listener_interface/old_importing_listeners.txt b/atest/robot/output/listener_interface/old_importing_listeners.txt index d773a9bfcea..b416969dedc 100644 --- a/atest/robot/output/listener_interface/old_importing_listeners.txt +++ b/atest/robot/output/listener_interface/old_importing_listeners.txt @@ -35,10 +35,17 @@ Listener With Wrong Number Of Arguments ... Creating instance failed: TypeError: Non Existing Listener - [Template] Check Syslog contains - Taking listener 'NonExistingListener' into use failed: + [Template] NONE + Run on python 2.x + ... Check Syslog contains + ... Taking listener 'NonExistingListener' into use failed: ... Importing listener 'NonExistingListener' failed: ... ImportError: No module named NonExistingListener${EMPTY TB} + Run on python 3.x + ... Check Syslog contains + ... Taking listener 'NonExistingListener' into use failed: + ... Importing listener 'NonExistingListener' failed: + ... ImportError: No module named 'NonExistingListener'${EMPTY TB} Java Listener [Tags] jybot diff --git a/atest/robot/test_libraries/error_msg_and_details.txt b/atest/robot/test_libraries/error_msg_and_details.txt index 91940093bba..32d0a18e994 100644 --- a/atest/robot/test_libraries/error_msg_and_details.txt +++ b/atest/robot/test_libraries/error_msg_and_details.txt @@ -50,9 +50,15 @@ Message Is Got Correctly If Java Exception Has 'null' Message Message And Internal Trace Are Removed From Details When Exception In Library [Template] NONE ${tc} = Verify Test Case And Error In Log Generic Failure foo != bar - Verify Python Traceback ${tc.kws[0].msgs[1]} exception raise exception, msg + Run on python 2.x + ... Verify Python Traceback ${tc.kws[0].msgs[1]} exception raise exception, msg + Run on python 3.x + ... Verify Python Traceback ${tc.kws[0].msgs[1]} exception raise exception(msg) ${tc} = Verify Test Case And Error In Log Non Generic Failure FloatingPointError: Too Large A Number !! - Verify Python Traceback ${tc.kws[0].msgs[1]} exception raise exception, msg + Run on python 2.x + ... Verify Python Traceback ${tc.kws[0].msgs[1]} exception raise exception, msg + Run on python 3.x + ... Verify Python Traceback ${tc.kws[0].msgs[1]} exception raise exception(msg) Message And Internal Trace Are Removed From Details When Exception In Java Library [Tags] jybot @@ -65,7 +71,10 @@ Message And Internal Trace Are Removed From Details When Exception In Java Libra Message and Internal Trace Are Removed From Details When Exception In External Code [Template] NONE ${tc} = Verify Test Case And Error In Log External Failure UnboundLocalError: Raised from an external object! - Verify Python Traceback ${tc.kws[0].msgs[1]} external_exception ObjectToReturn('failure').exception(name, msg) exception raise exception, msg + Run on python 2.x + ... Verify Python Traceback ${tc.kws[0].msgs[1]} external_exception ObjectToReturn('failure').exception(name, msg) exception raise exception, msg + Run on python 3.x + ... Verify Python Traceback ${tc.kws[0].msgs[1]} external_exception ObjectToReturn('failure').exception(name, msg) exception raise exception(msg) Message and Internal Trace Are Removed From Details When Exception In External Java Code [Tags] jybot diff --git a/atest/robot/test_libraries/library_imports_by_path.txt b/atest/robot/test_libraries/library_imports_by_path.txt index 038528513cf..e566e4402df 100644 --- a/atest/robot/test_libraries/library_imports_by_path.txt +++ b/atest/robot/test_libraries/library_imports_by_path.txt @@ -43,10 +43,16 @@ Importing Invalid Python File Fails Check Stderr Contains Importing test library '${path}' failed: ImportError: I'm not really a library! Inporting Dir Library Without Trailing "/" Fails - Check Stderr Contains Importing test library 'MyLibDir' failed: ImportError: No module named MyLibDir + Run on python 2.x + ... Check Stderr Contains Importing test library 'MyLibDir' failed: ImportError: No module named MyLibDir + Run on python 3.x + ... Check Stderr Contains Importing test library 'MyLibDir' failed: ImportError: No module named 'MyLibDir' Importing Non Python File Fails - Check Stderr Contains Importing test library 'java_libraries.html' failed: ImportError: No module named java_libraries + Run on python 2.x + ... Check Stderr Contains Importing test library 'java_libraries.html' failed: ImportError: No module named java_libraries + Run on python 3.x + ... Check Stderr Contains Importing test library 'java_libraries.html' failed: ImportError: No module named 'java_libraries' Importing Non Python Dir Fails Check Stderr Contains Test library 'library_scope/' does not exist. From 56d0ed1c5d9f1874241094082dcae202f5aa54c9 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 29 Sep 2013 20:46:10 +0200 Subject: [PATCH 025/214] [python3] run_atests: Little update and code separation of .txt file conversions --HG-- extra : transplant_source : o%A5%88%2C%C4%A5%17%3BY%113%FF%28%0A0C%D8%EA%84%B1 --- atest/run_atests.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/atest/run_atests.py b/atest/run_atests.py index 5b4966a9ea1..339af05bb85 100755 --- a/atest/run_atests.py +++ b/atest/run_atests.py @@ -82,8 +82,10 @@ pass else: print("Preparing for Python 3: %s" % path) + # Remove u prefixes from unicode literals: + text = re.sub(r'([\[(= ])u\'', r'\1\'', text) with open(path, 'w') as f: - f.write(re.sub(r'([\[( ])u\'', r'\1\'', text)) + f.write(text) do2to3 = False CURDIR = PY3ATESTDIR From 1525262bab037f77741bec299aec56af4b440c99 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 29 Sep 2013 20:51:55 +0200 Subject: [PATCH 026/214] [python3] setup: Changed name to robotframework-python3 for development --HG-- extra : transplant_source : %7E%1A%E5%CD%E6%E4%0Ew%E3%CC%92%C1%F1%B9%F8A/Q3%05 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a095c8ae7e9..9030b2c5341 100755 --- a/setup.py +++ b/setup.py @@ -50,7 +50,7 @@ SCRIPTS.append('robot_postinstall.py') setup( - name = 'robotframework', + name = 'robotframework-python3', version = get_version(sep=''), author = 'Robot Framework Developers', author_email = 'robotframework@gmail.com', From 0fe938c61eacf96a2a20d6ead9d90272c4af11e9 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 30 Sep 2013 08:17:26 +0200 Subject: [PATCH 027/214] [python3] testdata/standard_libraries/process: Made embedded print statements Python 2/3 compatible --HG-- extra : transplant_source : %0C%F7%29%05%05%24%83%3B%DBMB%1A%266fj%A4%B9I%CB --- .../process/newlines_and_encoding.txt | 14 +++---- .../process/process_library.txt | 40 +++++++++---------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/atest/testdata/standard_libraries/process/newlines_and_encoding.txt b/atest/testdata/standard_libraries/process/newlines_and_encoding.txt index 21d14675329..bc40e5cb10f 100644 --- a/atest/testdata/standard_libraries/process/newlines_and_encoding.txt +++ b/atest/testdata/standard_libraries/process/newlines_and_encoding.txt @@ -4,31 +4,31 @@ Resource resource.txt *** Test Cases *** Non-ascii in the command using shell=True - ${result}= Run Process python -c "print 'ööåöåöå'" shell=True + ${result}= Run Process python -c "print('ööåöåöå')" shell=True Result should equal ${result} stdout=ööåöåöå Non-ascii in the command using shell=False - ${result}= Run Process python -c print "ööåöåöå" + ${result}= Run Process python -c print("ööåöåöå") Result should equal ${result} stdout=ööåöåöå Non-ascii in the command with given stdout ${path}= Normalize Path %{TEMPDIR}/process-stdout.txt - ${result}= Run Process python -c print "ööåöåöå" shell=True stdout=${path} + ${result}= Run Process python -c 'print("ööåöåöå")' shell=True stdout=${path} Result should equal ${result} stdout=ööåöåöå [Teardown] Safe Remove File ${path} Newlines and trailing newline is removed - ${result}= Run Process python -c "print 'first line\\nsecond line\\nthird line'" shell=True cwd=${CURDIR} + ${result}= Run Process python -c "print('first line\\nsecond line\\nthird line')" shell=True cwd=${CURDIR} Result should equal ${result} stdout=first line\nsecond line\nthird line Non-ascii in the command arguments - ${result}= Run Process python -c "import os; print os.getenv('varri', '-');" shell=True env:varri=Öoa + ${result}= Run Process python -c "import os; print(os.getenv('varri', '-'));" shell=True env:varri=Öoa Should Be Equal ${result.stdout.strip()} Öoa Newline test using shell=True - ${result}= Run Process python -c "print 'hello'" shell=True + ${result}= Run Process python -c "print('hello')" shell=True Result should equal ${result} stdout=hello Newline test using shell=False - ${result}= Run Process python -c print "hello" + ${result}= Run Process python -c print("hello") Result should equal ${result} stdout=hello diff --git a/atest/testdata/standard_libraries/process/process_library.txt b/atest/testdata/standard_libraries/process/process_library.txt index 451b8f45520..13b37aac442 100644 --- a/atest/testdata/standard_libraries/process/process_library.txt +++ b/atest/testdata/standard_libraries/process/process_library.txt @@ -35,12 +35,12 @@ Switching active process Stop Some Process Change Current Working Directory - ${result}= Run Process python -c import os; print os.path.abspath(os.curdir); cwd=. - ${result2}= Run Process python -c import os; print os.path.abspath(os.curdir); cwd=.. + ${result}= Run Process python -c import os; print(os.path.abspath(os.curdir)); cwd=. + ${result2}= Run Process python -c import os; print(os.path.abspath(os.curdir)); cwd=.. Should Not Be Equal ${result.stdout} ${result2.stdout} Setting Stdout - ${result}= Run Process python -c "print 'hello'" shell=True stdout=%{TEMPDIR}/myfile_1.txt + ${result}= Run Process python -c "print('hello')" shell=True stdout=%{TEMPDIR}/myfile_1.txt ${output}= Get File %{TEMPDIR}/myfile_1.txt Should Not Be Empty ${output} Should Match ${output} ${result.stdout}* @@ -55,52 +55,52 @@ Setting Stderr Without Env Configuration the Environment Should Be As It Was Set Environment Variable normalvar normal - ${result}= Run Process python -c "import os; print os.getenv('normalvar', '-'), os.getenv('specialvar', '-');" shell=True + ${result}= Run Process python -c "import os; print('%s %s' % (os.getenv('normalvar', '-'), os.getenv('specialvar', '-')));" shell=True Should Be Equal ${result.stdout.strip()} normal - With Env: Configuration the Environment Should Contain Additional Variable Set Environment Variable normalvar normal - ${result}= Run Process python -c "import os; print os.getenv('normalvar', '-'), os.getenv('specialvar', '-');" shell=True env:specialvar=spessu + ${result}= Run Process python -c "import os; print('%s %s' % (os.getenv('normalvar', '-'), os.getenv('specialvar', '-')));" shell=True env:specialvar=spessu Should Be Equal ${result.stdout.strip()} normal spessu With Env= Configuration the Environment Should Contain Only Additional Variable Set Environment Variable normalvar normal ${setenv}= Create env dictionary specialvar spessu - ${result}= Run Process python -c "import os; print os.getenv('normalvar', '-'), os.getenv('specialvar', '-');" shell=True env=${setenv} + ${result}= Run Process python -c "import os; print('%s %s' % (os.getenv('normalvar', '-'), os.getenv('specialvar', '-')));" shell=True env=${setenv} Should Be Equal ${result.stdout.strip()} - spessu Setting Environment With Multiple Values Set Environment Variable normalvar normal - ${result}= Run Process python -c "import os; print os.getenv('normalvar', '-'), os.getenv('specialvar', '-'), os.getenv('diiba', '-');" shell=True env:specialvar=spessu env:diiba=daaba + ${result}= Run Process python -c "import os; print('%s %s %s' % (os.getenv('normalvar', '-'), os.getenv('specialvar', '-'), os.getenv('diiba', '-')));" shell=True env:specialvar=spessu env:diiba=daaba Should Be Equal ${result.stdout.strip()} normal spessu daaba Setting Environment Variable Overrides Original Set Environment Variable VARI original - ${result}= Run Process python -c "import os; print os.getenv('VARI', '-');" shell=True env:VARI=new + ${result}= Run Process python -c "import os; print(os.getenv('VARI', '-'));" shell=True env:VARI=new Should Be Equal ${result.stdout.strip()} new Setting Environment With Multiple Values Using Dictionary Set Environment Variable normalvar normal ${setenv}= Create env dictionary specialvar spessu diiba2 daaba2 - ${result}= Run Process python -c import os; print os.getenv('normalvar', '-'), os.getenv('specialvar', '-'), os.getenv('diiba2', '-'); env=${setenv} + ${result}= Run Process python -c import os; print('%s %s %s' % (os.getenv('normalvar', '-'), os.getenv('specialvar', '-'), os.getenv('diiba2', '-'))); env=${setenv} Should Be Equal ${result.stdout.strip()} - spessu daaba2 Unsupported Arguments Should Cause Error ${setenv}= Create Dictionary sp spessu - Run Keyword And Expect Error 'genv' is not supported by this keyword. Run Process python -c "import os; print os.environ;" shell=True genv=${setenv} - Run Keyword And Expect Error 'shellx' is not supported by this keyword. Run Process python -c "import os; print os.environ;" shellx=True + Run Keyword And Expect Error 'genv' is not supported by this keyword. Run Process python -c "import os; print(os.environ);" shell=True genv=${setenv} + Run Keyword And Expect Error 'shellx' is not supported by this keyword. Run Process python -c "import os; print(os.environ);" shellx=True Escaping equals sign - ${result}= Run Process python -c print 'stderr\=bar.buu' shell=True + ${result}= Run Process python -c "print('stderr\=bar.buu')" shell=True Result should match ${result} stdout=*stderr=bar.buu* Running a process in a shell - ${result}= Run Process python -c "print 'hello'" shell=True + ${result}= Run Process python -c "print('hello')" shell=True Result should equal ${result} stdout=hello - Run Keyword And Expect Error * Run Process python -c "print 'hello'" shell=${False} + Run Keyword And Expect Error * Run Process python -c "print('hello')" shell=${False} Input things to process - Start Process python -c "print 'inp %s' % raw_input()" shell=True + Start Process python -c "import sys; print('inp %s' % (input() if sys.version_info[0] \=\= 3 else raw_input()))" shell=True ${process}= Get Process Object Log ${process.stdin.write("some input\n")} Log ${process.stdin.flush()} @@ -108,13 +108,13 @@ Input things to process Should Match ${result.stdout} *inp some input* Process alias - ${handle}= Start Process python -c "print 'hello'" shell=True alias=hello + ${handle}= Start Process python -c "print('hello')" shell=True alias=hello ${pid_by_handle}= Get process id ${handle} ${pid_by_alias}= Get process id hello Should Be Equal ${pid_by_handle} ${pid_by_alias} Redirecting Stderr to Stdout - ${result}= Run Process python -c print 'hello';1/0 stderr=STDOUT + ${result}= Run Process python -c print('hello');1/0 stderr=STDOUT Should Match ${result.stdout} *hello* Should Match ${result.stdout} *ZeroDivisionError* Should Be Equal ${result.stderr} ${EMPTY} @@ -123,7 +123,7 @@ Redirecting Stderr to Stdout Redirecting Stderr to Stdout with filename ${path}= Normalize Path %{TEMPDIR}/filename.txt - ${result}= Run Process python -c print 'hello';1/0 stdout=${path} stderr=${path} + ${result}= Run Process python -c print('hello');1/0 stdout=${path} stderr=${path} Should Match ${result.stdout} *hello* Should Match ${result.stdout} *ZeroDivisionError* Should Match ${result.stderr} *hello* @@ -134,7 +134,7 @@ Redirecting Stderr to Stdout with filename Current working directory should be used with stdout and stderr Create Directory %{TEMPDIR}/hc - ${result}= Run Process python -c print 'moon kuu';1/0 cwd=%{TEMPDIR}/hc stdout=myout.txt + ${result}= Run Process python -c print('moon kuu');1/0 cwd=%{TEMPDIR}/hc stdout=myout.txt ... stderr=myerr.txt ${output}= Get File %{TEMPDIR}/hc/myout.txt ${output2}= Get File %{TEMPDIR}/hc/myerr.txt @@ -145,7 +145,7 @@ Current working directory should be used with stdout and stderr Current working directory should not be used with stdout and stderr when absolute path in use Create Directory %{TEMPDIR}/hc ${stdout_path}= Normalize Path %{TEMPDIR}/stdout.txt - ${result}= Run Process python -c print 'moon kuu';1/0 cwd=%{TEMPDIR}/hc stdout=${stdout_path} + ${result}= Run Process python -c print('moon kuu');1/0 cwd=%{TEMPDIR}/hc stdout=${stdout_path} ... stderr=stderr.txt ${stderr_path}= Normalize Path %{TEMPDIR}/hc/stderr.txt ${stdout}= Get File ${stdout_path} From 9ddc42224548ca9d059600b8fd6a63c2bb956bf8 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 30 Sep 2013 08:23:05 +0200 Subject: [PATCH 028/214] [python3] Process.ProcessConfig.__str__: Don't encode in Python 3 --HG-- extra : transplant_source : 1%E4%0A%09%E4W%8A%E4%95%C0%CD%27%F9%AA6M%A8%88%C9%B9 --- src/robot/libraries/Process.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/robot/libraries/Process.py b/src/robot/libraries/Process.py index 7026178f156..25b3d818898 100644 --- a/src/robot/libraries/Process.py +++ b/src/robot/libraries/Process.py @@ -14,6 +14,7 @@ from __future__ import with_statement +import sys import os import subprocess @@ -518,11 +519,14 @@ def _construct_env(self, env, rest): return env def __str__(self): - return encode_to_system("""\ + text = """\ cwd = %s stdout_stream = %s stderr_stream = %s shell = %r alias = %s env = %r""" % (self.cwd, self.stdout_stream, self.stderr_stream, - self.shell, self.alias, self.env)) + self.shell, self.alias, self.env) + if sys.version_info[0] == 3: + return text + return encode_to_system(text) From 226da9896aaa18a17dbd0b047339c3d5f97950a6 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 30 Sep 2013 08:25:46 +0200 Subject: [PATCH 029/214] [python3] etreewrapper: Special _open_file/_open_string_io methods for Python 3 --HG-- extra : transplant_source : %7F%D3%ED%28%FFj%F2-%E5g%CD%3E%00E%09%B4%BBm%DC%12 --- src/robot/utils/etreewrapper.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/robot/utils/etreewrapper.py b/src/robot/utils/etreewrapper.py index 1a407082b9b..28a4e8bf841 100644 --- a/src/robot/utils/etreewrapper.py +++ b/src/robot/utils/etreewrapper.py @@ -89,11 +89,22 @@ def _open_source_if_necessary(self): # it didn't close files it had opened. This caused problems with Jython # especially on Windows: http://bugs.jython.org/issue1598 # The bug has now been fixed in ET and worked around in Jython 2.5.2. - def _open_file(self, source): - return open(source, 'rb') - def _open_string_io(self, source): - return StringIO(source.encode('UTF-8')) + if sys.version_info[0] == 3: + + def _open_file(self, source): + return open(source, 'r') + + def _open_string_io(self, source): + return StringIO(source) + + else: + + def _open_file(self, source): + return open(source, 'rb') + + def _open_string_io(self, source): + return StringIO(source.encode('UTF-8')) else: From f563ac6218bdc2fc7b346c2009c471ed5feebdea Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 30 Sep 2013 08:27:31 +0200 Subject: [PATCH 030/214] [python3] XML.save_xml: Open file in binary mode --HG-- extra : transplant_source : %DE%2A%3D%8B%B1%206%AC%C0%A4%EC%C5tQ%E7%D2%03%04%030 --- src/robot/libraries/XML.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/robot/libraries/XML.py b/src/robot/libraries/XML.py index 9c478fada5a..5eb6cd22592 100644 --- a/src/robot/libraries/XML.py +++ b/src/robot/libraries/XML.py @@ -1173,7 +1173,9 @@ def save_xml(self, source, path, encoding='UTF-8'): kwargs = {'xml_declaration': True} if ET.VERSION >= '1.3' else {} # Need to explicitly open/close files because older ET versions don't # close files they open and Jython/IPY don't close them implicitly. - with open(path, 'w') as output: + # Opening in binary mode is important for Python 3, + # because the ElementTree writes encoded bytes. + with open(path, 'wb') as output: tree.write(output, encoding, **kwargs) From 3d0b413b204f80d91256932a3f2da47a6f6936b5 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 30 Sep 2013 08:51:01 +0200 Subject: [PATCH 031/214] [python3] README: Fork description --HG-- extra : transplant_source : Buu%D1x%0B%84%89%D4%94oh%93H9%8F%FD%A6%F0t --- README.txt | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.txt b/README.txt index 04e6ce3f4cd..5dcfa2c227f 100644 --- a/README.txt +++ b/README.txt @@ -1,3 +1,28 @@ +This is an unofficial Robot Framework Python 3 compatibility fork. +It also remains compatible with all officially supported +Python 2 platforms and versions, starting with 2.5. + +It uses the ``2to3`` tool in ``setup.py`` and ``atest/run_atests.py``. +The latter copies ``src/robot/`` and ``atest/`` to ``atest/python3/`` +before running the ``2to3`` script on them +and also converts some contents +of the Test Suite and Resource ``.txt`` files. + +``2to3`` can't handle everything... +Some fixers are disabled and there are also manual code changes. +The latter are mostly commented, with ``Python 3`` in the text, +or contain ``if sys.version_info[0] == 3``. +Manually changes in the acceptance Test Suites and Resources +mostly use ``Run on python 2.x`` and ``3.x`` Keywords for switching. + +Most of the acceptance tests are already passing with Python 3. +Only 109/3110 are currently failing on my machine, +but this is mostly related to the tests themselves, +which need some further workarounds, switches and conversions. + +-- Stefan Zimmermann + + Robot Framework =============== From eabf4833d81e091aaaef7e19551faa7f2f1ef599 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 30 Sep 2013 09:19:58 +0200 Subject: [PATCH 032/214] [python3] README: Diff url --HG-- extra : transplant_source : %C1%08K%FA%E8%12%A4%0F%D2%D7%A3x%E4%2A%81%0D%CD%A4%9Df --- README.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.txt b/README.txt index 5dcfa2c227f..d4745dd92b5 100644 --- a/README.txt +++ b/README.txt @@ -15,8 +15,12 @@ or contain ``if sys.version_info[0] == 3``. Manually changes in the acceptance Test Suites and Resources mostly use ``Run on python 2.x`` and ``3.x`` Keywords for switching. +You can also look at this URL for a complete diff: + +https://bitbucket.org/userzimmermann/robotframework-python3/compare/default..c148e32#diff + Most of the acceptance tests are already passing with Python 3. -Only 109/3110 are currently failing on my machine, +Only 109/3111 are currently failing on my machine, but this is mostly related to the tests themselves, which need some further workarounds, switches and conversions. From 5314290f0e4c1f272572292f267536c9155d61fe Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 2 Oct 2013 09:51:05 +0200 Subject: [PATCH 033/214] [python3] restreader: Workarounds for new bytes/str issues --HG-- extra : transplant_source : %7E%3C%40%E8%DF%0B%8A%93b%1B%F3N%0DJ9%27%3B%FCWP --- src/robot/parsing/restreader.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/robot/parsing/restreader.py b/src/robot/parsing/restreader.py index 30dc1e8922c..3295724493f 100644 --- a/src/robot/parsing/restreader.py +++ b/src/robot/parsing/restreader.py @@ -12,6 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys +if sys.version_info[0] == 3: + from io import BytesIO from cStringIO import StringIO from .htmlreader import HtmlReader @@ -34,11 +37,17 @@ def read(self, rstfile, rawdata): return self._read_html(doctree, rawdata) def _read_text(self, data, rawdata): - txtfile = StringIO(data.encode('UTF-8')) + if sys.version_info[0] == 3: + txtfile = StringIO(data) + else: + txtfile = StringIO(data.encode('UTF-8')) return TxtReader().read(txtfile, rawdata) def _read_html(self, doctree, rawdata): - htmlfile = StringIO() + if sys.version_info[0] == 3: + htmlfile = BytesIO() + else: + htmlfile = StringIO() htmlfile.write(publish_from_doctree( doctree, writer_name='html', settings_overrides={'output_encoding': 'UTF-8'})) From 63f5ebee605190d7d81129615c071083302b4245 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 4 Oct 2013 20:27:18 +0200 Subject: [PATCH 034/214] [python3] TsvReader.read(): Always row.rstrip() --HG-- extra : transplant_source : %AA1%96%D5%A1%7Bd%2A%02%D8%3D%A7%A7%0B%3Du%92%BDD%2B --- src/robot/parsing/tsvreader.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/robot/parsing/tsvreader.py b/src/robot/parsing/tsvreader.py index b5571c337fb..48078a024ba 100644 --- a/src/robot/parsing/tsvreader.py +++ b/src/robot/parsing/tsvreader.py @@ -27,6 +27,7 @@ def read(self, tsvfile, populator): # 2to3 changes `unicode` to `str`. if type(row) is not unicode: row = self._decode_row(row, index == 0) + row = row.rstrip() cells = [self._process(cell) for cell in self.split_row(row)] name = cells and cells[0].strip() or '' if name.startswith('*') and \ @@ -42,7 +43,7 @@ def _decode_row(self, row, is_first): row = row.decode('UTF-8') if NBSP in row: row = row.replace(NBSP, ' ') - return row.rstrip() + return row @classmethod def split_row(cls, row): From 40194fb1309afdc4d9a5ee2301d60975cecb6480 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 8 Oct 2013 14:31:53 +0200 Subject: [PATCH 035/214] [python3] atest/robot/rebot/combine.txt: .has_key()-->.__contains__() --HG-- extra : transplant_source : %92%B9%1B%AAQ%C9%00%3CM%2A%CD%96%ED%3E-%DB%5BhP%14 --- atest/robot/rebot/combine.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atest/robot/rebot/combine.txt b/atest/robot/rebot/combine.txt index 4dd5238e985..47597e1ba21 100644 --- a/atest/robot/rebot/combine.txt +++ b/atest/robot/rebot/combine.txt @@ -140,7 +140,7 @@ Elapsed Time Should Be Written To Output When Start And End Time Are Not Known ${originals} = Get Elements ${COMB OUT 1} suite/suite/status Should Not Be Equal ${originals[0].attrib['starttime']} N/A Should Not Be Equal ${originals[0].attrib['endtime']} N/A - Should Not Be True ${originals[0].attrib.has_key('elapsedtime')} + Should Not Be True ${originals[0].attrib.__contains__('elapsedtime')} Combined Suite Names Are Correct In Statistics ${suites} = Get Suite Stat Nodes ${COMB OUT 1} From 6e2f321f5b9d4863ec5fbeb1aefe3d2bcbfb126d Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 8 Oct 2013 15:31:39 +0200 Subject: [PATCH 036/214] [python3] atest: Workarounds for direct repr of non-ascii chars in Python 3 strs (without \x or \u) --HG-- extra : transplant_source : C%ED%3F%1B2%25%84%0B%FC%DC%1E%F4%9F%5C%0Bb%FBNQ%E3 --- atest/run_atests.py | 8 ++++++++ atest/testdata/core/expbytevalues.py | 2 +- atest/testdata/variables/non_string_variables.py | 4 ++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/atest/run_atests.py b/atest/run_atests.py index f523b5277fc..fce1fd6b9fe 100755 --- a/atest/run_atests.py +++ b/atest/run_atests.py @@ -86,6 +86,14 @@ print("Preparing for Python 3: %s" % path) # Remove u prefixes from unicode literals: text = re.sub(r'([\[(= ])u\'', r'\1\'', text) + text = re.sub( + r'\\\\x([0-9a-f]{2})', + lambda match: chr(int(match.group(1), 16)), + text) + text = re.sub( + r'\\\\u([0-9a-f]{4})', + lambda match: chr(int(match.group(1), 16)), + text) with open(path, 'w') as f: f.write(text) diff --git a/atest/testdata/core/expbytevalues.py b/atest/testdata/core/expbytevalues.py index cae820ad806..3e0a05eeecc 100644 --- a/atest/testdata/core/expbytevalues.py +++ b/atest/testdata/core/expbytevalues.py @@ -3,7 +3,7 @@ def get_variables(interpreter=None): - if not _running_on_iron_python(interpreter): + if sys.version_info[0] < 3 and not _running_on_iron_python(interpreter): messages = {'exp_return_msg': 'ty\\xf6paikka', 'exp_error_msg': 'hyv\\xe4', 'exp_log_msg': '\\xe4ity'} diff --git a/atest/testdata/variables/non_string_variables.py b/atest/testdata/variables/non_string_variables.py index bd9164d1691..d16bfcb0939 100644 --- a/atest/testdata/variables/non_string_variables.py +++ b/atest/testdata/variables/non_string_variables.py @@ -16,6 +16,10 @@ def get_variables(interpreter=None): return variables def _get_interpreter_specific_strs(interpreter): + if sys.version_info[0] == 3: + return {'byte_string_str': 'hyv\xe4', + 'list_str': str([1, '\xe4', '\xe4']), + 'dict_str': str({'\xe4': '\xe4'})} if not _running_on_iron_python(interpreter): return {'byte_string_str': 'hyv\\xe4', 'list_str': "[1, '\\xe4', u'\\xe4']", From 01d5d2169b98b74f631e0742d5d542dd17aa6418 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 9 Oct 2013 11:38:55 +0200 Subject: [PATCH 037/214] [python3] BuiltIn: Compatibility workarounds in _get_type() and _convert_to_bin_oct_hex() --HG-- extra : transplant_source : %EAC%D6%BC%D2%8A%F5%CB%28i%B6%A0%60%2A%BE%84/v%2C%DC --- src/robot/libraries/BuiltIn.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/robot/libraries/BuiltIn.py b/src/robot/libraries/BuiltIn.py index b44b522ee31..90db0b66813 100644 --- a/src/robot/libraries/BuiltIn.py +++ b/src/robot/libraries/BuiltIn.py @@ -14,6 +14,7 @@ import re import time +import sys from robot.api import logger from robot.errors import (ContinueForLoop, DataError, ExecutionFailed, @@ -206,7 +207,10 @@ def _convert_to_bin_oct_hex(self, method, item, base, prefix, length, prefix = '-' + prefix ret = ret[1:] if len(ret) > 1: # oct(0) -> '0' (i.e. has no prefix) - prefix_length = {bin: 2, oct: 1, hex: 2}[method] + prefix_length = {bin: 2, + oct: (2 if sys.version_info[0] == 3 else 1), + hex: 2 + }[method] ret = ret[prefix_length:] if length: ret = ret.rjust(self._convert_to_integer(length), '0') @@ -418,8 +422,9 @@ def _log_types(self, *args): self.log('\n'.join(msg)) def _get_type(self, arg): - # In IronPython type(u'x') is str. We want to report unicode anyway. - if isinstance(arg, unicode): + # In IronPython type(u'x') is str. We want to report unicode anyway, + # except for Python 3. + if sys.version_info[0] < 3 and isinstance(arg, unicode): return "" return str(type(arg)) From bdd895eb22fcd7bbbd439de3db79df17f7afae3f Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 9 Oct 2013 11:42:39 +0200 Subject: [PATCH 038/214] [python3] BuiltIn.Create Bytes --HG-- extra : transplant_source : GW%07%5C%E8%D2%C4c5%CE%A14%B8xq%13%92%87J%BF --- src/robot/libraries/BuiltIn.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/robot/libraries/BuiltIn.py b/src/robot/libraries/BuiltIn.py index 90db0b66813..e691c364ecd 100644 --- a/src/robot/libraries/BuiltIn.py +++ b/src/robot/libraries/BuiltIn.py @@ -309,6 +309,22 @@ def create_list(self, *items): """ return list(items) + def create_bytes(self, string): + """Creates a bytes object from `string` by evaluating 'b"%(string)s"'. + + Use two backslashes for writing bytes in hex: \\\\xXX + """ + try: + if type(string) is bytes: + return string + except NameError: + pass + string = string.replace('"', '\\"') + try: + return eval('b"%s"' % string) + except SyntaxError: # Python 2.5 + return eval('"%s"' % string) + class _Verify: From 1fb358c80ac7836a933a51c96b3a986eb4ca79f3 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 9 Oct 2013 11:44:11 +0200 Subject: [PATCH 039/214] [python3] Compatible String.Should Be Byte String --HG-- extra : transplant_source : 5%EB%AE%CF%BD%28%B0%1Cj%2A%87%B4-%2C%9E%20%E9%2A%9D%FA --- src/robot/libraries/String.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/robot/libraries/String.py b/src/robot/libraries/String.py index a4ffce6b612..a0c5b477086 100644 --- a/src/robot/libraries/String.py +++ b/src/robot/libraries/String.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys import re from fnmatch import fnmatchcase from random import randint @@ -480,7 +481,7 @@ def should_be_byte_string(self, item, msg=None): New in Robot Framework 2.7.7. """ - if not isinstance(item, str): + if not isinstance(item, bytes if sys.version_info[0] == 3 else str): self._fail(msg, "'%s' is not a byte string.", item) def should_be_lowercase(self, string, msg=None): From 019ffb5e41f81d0aa66bfca26ace4972b3e386bb Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 9 Oct 2013 11:51:17 +0200 Subject: [PATCH 040/214] [python3] run_atests: More .txt conversion and PYTHON3 variable --HG-- extra : transplant_source : %A2%3EWG%19%DA%B96_%DB%0E%0D%E2H%AB%F4%0C%F9%90%5D --- atest/run_atests.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/atest/run_atests.py b/atest/run_atests.py index fce1fd6b9fe..92933a01669 100755 --- a/atest/run_atests.py +++ b/atest/run_atests.py @@ -86,14 +86,22 @@ print("Preparing for Python 3: %s" % path) # Remove u prefixes from unicode literals: text = re.sub(r'([\[(= ])u\'', r'\1\'', text) + # Replace hex codes in strings + # with actual unicode characters, + # if not used to create bytes objects: text = re.sub( - r'\\\\x([0-9a-f]{2})', - lambda match: chr(int(match.group(1), 16)), + r'(.*)\\\\x([0-9a-f]{2})', + lambda match: ( + chr(int(match.group(2), 16)) + if not 'bytes' in match.group(1).lower() + else match.group(0)), text) text = re.sub( r'\\\\u([0-9a-f]{4})', lambda match: chr(int(match.group(1), 16)), text) + # Remove L suffixes from integer literals: + text = re.sub(r'([1-9][0-9]+)L', r'\1', text) with open(path, 'w') as f: f.write(text) @@ -117,6 +125,7 @@ --metadata Platform:%(PLATFORM)s --variable INTERPRETER:%(INTERPRETER)s --variable PYTHON:%(PYTHON)s +--variable PYTHON3:%(PYTHON3)s --variable JYTHON:%(JYTHON)s --variable IRONPYTHON:%(IRONPYTHON)s --variable STANDALONE_JYTHON:NO @@ -149,6 +158,9 @@ def atests(interpreter_path, *params): 'OUTPUTDIR' : resultdir, 'INTERPRETER': interpreter_path, 'PYTHON': interpreter_path if 'python' in interpreter else '', + 'PYTHON3': interpreter_path + if 'python' in interpreter and sys.version_info[0] == 3 + else '', 'JYTHON': interpreter_path if 'jython' in interpreter else '', 'IRONPYTHON': interpreter_path if 'ipy' in interpreter else '', 'PLATFORM': sys.platform, From b9250aa843868c2b850b727aeb88ff5a8e4fe1f3 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 9 Oct 2013 11:53:30 +0200 Subject: [PATCH 041/214] [python3] atest: string/encode_decode: Use BuiltIn.Create Bytes --HG-- extra : transplant_source : %82%86%60%99%B5T%22%80%8B%89%0Fu%EB%21W%E4%5B%9C%B7%08 --- .../testdata/standard_libraries/string/encode_decode.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/atest/testdata/standard_libraries/string/encode_decode.txt b/atest/testdata/standard_libraries/string/encode_decode.txt index 1f72b9ceea0..5c505b0eb7b 100644 --- a/atest/testdata/standard_libraries/string/encode_decode.txt +++ b/atest/testdata/standard_libraries/string/encode_decode.txt @@ -28,7 +28,8 @@ Encode Non-ASCII String To Bytes Using Incompatible Encoding And Error Handler Byte Strings Should Be Equal ${bytes} Hyv? Decode ASCII Bytes To String - ${string} = Decode Bytes To String Hello, world! UTF-8 + ${bytes} = Create Bytes Hello, world! + ${string} = Decode Bytes To String ${bytes} UTF-8 Should Be Equal ${string} Hello, world! Decode Non-ASCII Bytes To String @@ -50,13 +51,13 @@ Decode Non-ASCII Bytes To String Using Incompatible Encoding And Error Handler *** Keywords *** Create Byte String Variables - ${ISO-8859-1} = Evaluate "Hyv\\xe4" - ${UTF-8} = Evaluate "Hyv\\xc3\\xa4" + ${ISO-8859-1} = Create Bytes Hyv\\xe4 + ${UTF-8} = Create Bytes Hyv\\xc3\\xa4 Set Suite Variable ${ISO-8859-1} Set Suite Variable ${UTF-8} Byte Strings Should Be Equal [Arguments] ${bytes} ${expected} Should Be Byte String ${bytes} - ${expected} = Evaluate "${expected}" + ${expected} = Create Bytes ${expected} Should Be Equal ${bytes} ${expected} From be1d3c5de6ba87cd43d1825ee861a19a91cefd94 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 9 Oct 2013 11:58:27 +0200 Subject: [PATCH 042/214] [python3] atest: builtin: class '...'> and Unicode Type to Str On Python 3.x --HG-- extra : transplant_source : %3D%E2%A2%81%90%C3%11%22%9E%D6%23C%EF%D3%00%0D%E1%BBL%FE --- .../robot/standard_libraries/builtin/converter.txt | 12 +++++++++++- atest/robot/standard_libraries/builtin/verify.txt | 13 ++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/atest/robot/standard_libraries/builtin/converter.txt b/atest/robot/standard_libraries/builtin/converter.txt index c7ff387a5e3..a20f95666c6 100644 --- a/atest/robot/standard_libraries/builtin/converter.txt +++ b/atest/robot/standard_libraries/builtin/converter.txt @@ -69,4 +69,14 @@ Create List Verify argument type message [Arguments] ${msg} ${type1} - Check log message ${msg} Argument types are:\n + ${type1} = Unicode Type to Str On Python 3.x ${type1} + Run on python 2.x + ... Check log message ${msg} Argument types are:\n + Run on python 3.x + ... Check log message ${msg} Argument types are:\n + +Unicode Type to Str On Python 3.x + [Arguments] ${type} + ${type} = Set Variable If "${PYTHON3}" and "${type}" == "unicode" + ... str ${type} + [Return] ${type} diff --git a/atest/robot/standard_libraries/builtin/verify.txt b/atest/robot/standard_libraries/builtin/verify.txt index 5f532207a95..883d262f5ca 100644 --- a/atest/robot/standard_libraries/builtin/verify.txt +++ b/atest/robot/standard_libraries/builtin/verify.txt @@ -234,10 +234,21 @@ Verify argument type message [Arguments] ${msg} ${type1} ${type2} ${type1} = Str Type to Unicode On IronPython ${type1} ${type2} = Str Type to Unicode On IronPython ${type2} - Check log message ${msg} Argument types are:\n\n + ${type1} = Unicode Type to Str On Python 3.x ${type1} + ${type2} = Unicode Type to Str On Python 3.x ${type2} + Run on python 2.x + ... Check log message ${msg} Argument types are:\n\n + Run on python 3.x + ... Check log message ${msg} Argument types are:\n\n Str Type to Unicode On IronPython [Arguments] ${type} ${type} = Set Variable If "${IRONPYTHON}" and "${type}" == "str" ... unicode ${type} [Return] ${type} + +Unicode Type to Str On Python 3.x + [Arguments] ${type} + ${type} = Set Variable If "${PYTHON3}" and "${type}" == "unicode" + ... str ${type} + [Return] ${type} From 734d1eb3e1e799a13b570ebf93ca767aee647742 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 9 Oct 2013 11:59:08 +0200 Subject: [PATCH 043/214] [python3] atest: Several small compatibility workarounds --HG-- extra : transplant_source : %DA%0C%2A%CD%D1%03i%F2%0D_%5DP%1A%FFo%D8%C8%D0%9Dh --- atest/robot/libdoc/doc_format.txt | 2 +- atest/testdata/running/for.txt | 2 +- atest/testdata/standard_libraries/builtin/evaluate.txt | 8 ++++---- .../standard_libraries/builtin/numbers_to_convert.py | 3 ++- atest/testdata/standard_libraries/builtin/verify.txt | 6 +++--- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/atest/robot/libdoc/doc_format.txt b/atest/robot/libdoc/doc_format.txt index b1aad59ad33..4233f9cde2c 100644 --- a/atest/robot/libdoc/doc_format.txt +++ b/atest/robot/libdoc/doc_format.txt @@ -76,6 +76,6 @@ Format should be Element Attribute Should Be ${LIBDOC} format ${expected} Fail test non-critically if docutils is not installed - ${output} = Run ${INTERPRETER} -c "import docutils; print 'OK'" + ${output} = Run ${INTERPRETER} -c "import docutils; print('OK')" Run Keyword If """${output}""" != "OK" ... Fail This test requires `docutils` to be installed -regression diff --git a/atest/testdata/running/for.txt b/atest/testdata/running/for.txt index dae99d8775a..a8899cf3458 100644 --- a/atest/testdata/running/for.txt +++ b/atest/testdata/running/for.txt @@ -233,7 +233,7 @@ For In Range :FOR ${i} IN RANGE 100 \ @{var} = List @{var} ${i} \ Log i: ${i} - Fail Unless @{var} == range(100) + Fail Unless @{var} == list(range(100)) For In Range With Start And Stop @{var} = List diff --git a/atest/testdata/standard_libraries/builtin/evaluate.txt b/atest/testdata/standard_libraries/builtin/evaluate.txt index 3f156ae7330..98ce3741386 100644 --- a/atest/testdata/standard_libraries/builtin/evaluate.txt +++ b/atest/testdata/standard_libraries/builtin/evaluate.txt @@ -24,11 +24,11 @@ Evaluate Evaluate INVALID Evaluate With Modules - [Documentation] FAIL REGEXP: ImportError: [Nn]o module named nonex_module + [Documentation] FAIL REGEXP: ImportError: [Nn]o module named '?nonex_module'? ${ceil} = Evaluate math.ceil(1.001) math Should Be Equal ${ceil} ${2} - ${random} = Evaluate random.randint(0, sys.maxint) random,sys - ${maxint} ${sep} ${x} ${y} = Evaluate sys.maxint, os.sep, re.escape('+'), '\\+' sys, re,,,,, glob, os,robot,,, - Should Be True 0 <= ${random} <= ${maxint} + ${random} = Evaluate random.randint(0, sys.maxunicode) random,sys + ${maxunicode} ${sep} ${x} ${y} = Evaluate sys.maxunicode, os.sep, re.escape('+'), '\\+' sys, re,,,,, glob, os,robot,,, + Should Be True 0 <= ${random} <= ${maxunicode} Should Be Equal ${x} ${y} Evaluate 1 nonex_module diff --git a/atest/testdata/standard_libraries/builtin/numbers_to_convert.py b/atest/testdata/standard_libraries/builtin/numbers_to_convert.py index bbc86e85a11..7a37cbda9e2 100644 --- a/atest/testdata/standard_libraries/builtin/numbers_to_convert.py +++ b/atest/testdata/standard_libraries/builtin/numbers_to_convert.py @@ -22,7 +22,8 @@ class MyObject: def __init__(self, value): self.value = value def __int__(self): - return 42 / self.value + # Use // (explicit int div) for Python 3 compatibility: + return 42 // self.value def __str__(self): return 'MyObject' diff --git a/atest/testdata/standard_libraries/builtin/verify.txt b/atest/testdata/standard_libraries/builtin/verify.txt index 113df82f7ab..f458c4a5bde 100644 --- a/atest/testdata/standard_libraries/builtin/verify.txt +++ b/atest/testdata/standard_libraries/builtin/verify.txt @@ -137,14 +137,14 @@ Should Be Equal As Numbers ${STR1}.000001 ${STR1}.${STR0}${STR0}${STR1} Only this message False Should Be Equal As Numbers With Precision - [Documentation] FAIL Failure: 110.0 != 150.0 + [Documentation] FAIL Failure: 110.0 != 130.0 [Template] Should Be Equal As Numbers 1.123 1.456 precision=0 1.123 ${1.1} precision=1 ${1.123} ${1.12} precision=2 1123 1456 precision=-3 - 112 145 precision=-2 - 112 145 Failure precision=-1 + 112 134 precision=-2 + 112 134 Failure precision=-1 Should Not Be Equal As Strings [Documentation] FAIL These strings most certainly should not be equal From 3726d2455df79510c521a9ddfe3258f08b427edb Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 9 Oct 2013 14:20:17 +0200 Subject: [PATCH 044/214] [python3] OperatingSystem.Start Process: textio argument --HG-- extra : transplant_source : %FB%27%E5%C3%5C%13%D9%E9%F53%0D%CB%8F%93%F4%1C%B28%C8%00 --- src/robot/libraries/OperatingSystem.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/robot/libraries/OperatingSystem.py b/src/robot/libraries/OperatingSystem.py index 3bf00a7a98c..3e0f3d7bf2c 100644 --- a/src/robot/libraries/OperatingSystem.py +++ b/src/robot/libraries/OperatingSystem.py @@ -201,7 +201,7 @@ def _run(self, command): rc = process.close() return rc, stdout - def start_process(self, command, stdin=None, alias=None): + def start_process(self, command, stdin=None, alias=None, textio=False): """It is recommended to use same keyword from Process library instead. Starts the given command as a background process. @@ -228,6 +228,10 @@ def start_process(self, command, stdin=None, alias=None): keyword, but redirecting is done when the process is started and not by adding '2>&1' to the command. + Setting `textio` to any non-false value, such as `textio=True`, + the command's input/output streams will be opened in text mode, + working with `str` instead of `bytes` in Python 3.x. + Example: | Start Process | /path/longlasting.sh | | Do Something | | @@ -235,7 +239,7 @@ def start_process(self, command, stdin=None, alias=None): | Should Contain | ${output} | Expected text | | [Teardown] | Stop All Processes | """ - process = _Process2(command, stdin) + process = _Process2(command, stdin, textio=bool(textio)) self._info("Running command '%s'" % process) return PROCESSES.register(process, alias) @@ -1303,11 +1307,13 @@ def _process_output(self, stdout): class _Process2(_Process): - def __init__(self, command, input_): + def __init__(self, command, input_, textio=False): self._command = self._process_command(command) + ## raise RuntimeError(str(text_mode)) p = subprocess.Popen(self._command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - close_fds=os.sep=='/') + close_fds=os.sep=='/', + universal_newlines=textio) stdin, self.stdout = p.stdin, p.stdout if input_: stdin.write(input_) From f587a49ae7f846eaaa0ac0aecb852ba6ff64d2e0 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 9 Oct 2013 14:22:03 +0200 Subject: [PATCH 045/214] [python3] atest: Use textio=True for OperatingSystem.Start Process --HG-- extra : transplant_source : %98%FC%BC%FE%D9%26%7Do%90%7D%FF%C6%BEc%D2%7Bx%BF%3C%1D --- .../operating_system/start_process.txt | 42 +++++++++---------- .../process/start_process_preferences.txt | 4 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/atest/testdata/standard_libraries/operating_system/start_process.txt b/atest/testdata/standard_libraries/operating_system/start_process.txt index 33453ab6990..59f592dad15 100644 --- a/atest/testdata/standard_libraries/operating_system/start_process.txt +++ b/atest/testdata/standard_libraries/operating_system/start_process.txt @@ -10,40 +10,40 @@ ${TEMP FILE} ${CURDIR}${/}robot-start-process.tmp *** Test Cases *** Start Process - ${index} = Start Process ${PROG} 0 hello + ${index} = Start Process ${PROG} 0 hello textio=True Equals ${index} ${2} ${out} = Read Process Output Equals ${out} hello - Start Process ${PROG} 0 hi + Start Process ${PROG} 0 hi textio=True ${out} = Read Process Output Equals ${out} hi Stderr Is Redirected To Stdout - Start Process ${PROG} 0 hey error + Start Process ${PROG} 0 hey error textio=True ${out} = Read Process Output Should Match Regexp ${out} ^(hey\nerror|error\nhey)$ It Should Be Possble To Start Background Process - Start Process ${PROG} 0 hey error & + Start Process ${PROG} 0 hey error & textio=True ${out} = Read Process Output Should Match Regexp ${out} ^(hey\nerror|error\nhey)$ Start Writable Process - Start Process ${WRITABLE_PROG} hello world + Start Process ${WRITABLE_PROG} hello world textio=True ${output} = Read Process Output Equals ${output} HELLO WORLD Cannot Read From A Stopped Process [Documentation] FAIL Cannot read from a closed process - Start Process ${PROG} 0 hello + Start Process ${PROG} 0 hello textio=True ${output} = Read Process Output ${output} = Read Process Output Switch Process - ${first} = Start Process ${PROG} 0 hello - ${second} = Start Process ${PROG} 0 world - Start Process ${WRITABLE_PROG} hello world alias - Start Process ${PROG} 0 olleh + ${first} = Start Process ${PROG} 0 hello textio=True + ${second} = Start Process ${PROG} 0 world textio=True + Start Process ${WRITABLE_PROG} hello world alias textio=True + Start Process ${PROG} 0 olleh textio=True ${output} = Read Process Output Equals ${output} olleh Switch Process ${first} @@ -58,10 +58,10 @@ Switch Process Lives Between Tests Setup [Documentation] Starts a process used in next test case - Start Process ${PROG} 0 from_test_case ${EMPTY} test case process + Start Process ${PROG} 0 from_test_case ${EMPTY} test case process textio=True Lives Between Tests - [Setup] Start Process ${PROG} 0 from_test_setup ${EMPTY} test setup process + [Setup] Start Process ${PROG} 0 from_test_setup ${EMPTY} test setup process textio=True Switch Process suite setup process ${output} = Read Process Output Equals ${output} from_suite_setup @@ -74,45 +74,45 @@ Lives Between Tests Stop All [Documentation] FAIL No active processes - ${index} = Start Process ${PROG} 0 hello + ${index} = Start Process ${PROG} 0 hello textio=True Start Process ${PROG} 0 hello Stop All Processes - ${index} = Start Process ${PROG} 0 hello + ${index} = Start Process ${PROG} 0 hello textio=True Equals ${index} ${1} Stop All Processes Read Process Output Stopping Already Stopped Processes Is OK - Start Process ${PROG} 0 hello + Start Process ${PROG} 0 hello textio=True ${output} = Read Process Output Stop Process Stop Process - Start Process ${PROG} 0 hello + Start Process ${PROG} 0 hello textio=True Stop Process Stop Process Redirecting Stdout To File - Start Process ${PROG} 0 hello > ${TEMP FILE} + Start Process ${PROG} 0 hello > ${TEMP FILE} textio=True Output and Temp File Should Be ${EMPTY} hello [Teardown] Remove File ${TEMP FILE} Redirecting Stderr To File - Start Process ${PROG} 0 hello world 2> ${TEMP FILE} + Start Process ${PROG} 0 hello world 2> ${TEMP FILE} textio=True Output and Temp File Should Be hello world [Teardown] Remove File ${TEMP FILE} Redirecting Stderr To Stdout - Start Process ${PROG} 0 hello world 2>&1 + Start Process ${PROG} 0 hello world 2>&1 textio=True Output Should Be ^(hello\nworld|world\nhello)$ Reading Output With Lot Of Data In Stdout And Stderr - Start Process ${PROG} 0 hello world 15000 + Start Process ${PROG} 0 hello world 15000 textio=True ${out} = Read Process Output Length Should Be ${out} ${12*15000-1} *** Keywords *** My Setup - ${index} = Start Process ${PROG} 0 from_suite_setup ${EMPTY} suite setup process + ${index} = Start Process ${PROG} 0 from_suite_setup ${EMPTY} suite setup process textio=True Equals ${index} ${1} Output And Temp File Should Be diff --git a/atest/testdata/standard_libraries/process/start_process_preferences.txt b/atest/testdata/standard_libraries/process/start_process_preferences.txt index 52bcb3153ec..3edba846de0 100644 --- a/atest/testdata/standard_libraries/process/start_process_preferences.txt +++ b/atest/testdata/standard_libraries/process/start_process_preferences.txt @@ -6,7 +6,7 @@ Resource resource.txt *** Test Cases *** Explicitly run Operating System library keyword - ${handle}= OperatingSystem.Start Process python -c "import os; print os.path.abspath(os.curdir);" + ${handle}= OperatingSystem.Start Process python -c "import os; print os.path.abspath(os.curdir);" textio=True ${out}= Read Process Output Explicitly run Process library keyword @@ -22,7 +22,7 @@ Implicitly run Process library keyword Implicitly run Operating System library keyword when library search order is set Set Library Search Order OperatingSystem - ${handle}= Start Process python -c "import os; print os.path.abspath(os.curdir);" + ${handle}= Start Process python -c "import os; print os.path.abspath(os.curdir);" textio=True ${out}= Read Process Output [Teardown] Set Library Search Order ${EMPTY} From f0c853492cd4443dfed031c54fbca491e1d7da86 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 9 Oct 2013 14:28:51 +0200 Subject: [PATCH 046/214] [python3] Reverted Python/Jython 2.5 compatibility fixes for running run_atests.py --HG-- extra : transplant_source : r%F9K_%7F%A7b%8F%A9%EA%C2A%EE%24%10Pe%FC%1B%20 --- atest/resources/TestHelper.py | 2 -- atest/resources/read_interpreter.py | 18 +++--------------- atest/robot/libdoc/LibDocLib.py | 7 +------ atest/robot/running/nolog.py | 2 -- atest/run_atests.py | 1 - 5 files changed, 4 insertions(+), 26 deletions(-) diff --git a/atest/resources/TestHelper.py b/atest/resources/TestHelper.py index 60e7cb3045c..b9de21ee81e 100644 --- a/atest/resources/TestHelper.py +++ b/atest/resources/TestHelper.py @@ -1,5 +1,3 @@ -from __future__ import with_statement - import os import sys from stat import S_IREAD, S_IWRITE diff --git a/atest/resources/read_interpreter.py b/atest/resources/read_interpreter.py index 9c00af107fe..71f8d4ccfc4 100644 --- a/atest/resources/read_interpreter.py +++ b/atest/resources/read_interpreter.py @@ -1,24 +1,12 @@ import re -try: - from collections import namedtuple -except ImportError: - pass +from collections import namedtuple from robot.utils import ET MATCHER = re.compile(r'.*\((\w*) (.*) on (.*)\)') -try: - Interpreter = namedtuple('Interpreter', ['interpreter', 'version', 'platform']) -except NameError: - class Interpreter(tuple): - def __new__(cls, *values): - return tuple.__new__(cls, values) - - def __init__(self, interpreter, version, platform): - self.interpreter = interpreter - self.version = version - self.platform = platform +Interpreter = namedtuple('Interpreter', ['interpreter', 'version', 'platform']) + def get_interpreter(output): tree = ET.parse(output) diff --git a/atest/robot/libdoc/LibDocLib.py b/atest/robot/libdoc/LibDocLib.py index 7272ba1bf99..7361931778c 100644 --- a/atest/robot/libdoc/LibDocLib.py +++ b/atest/robot/libdoc/LibDocLib.py @@ -1,9 +1,4 @@ -from __future__ import with_statement - -try: - import json -except ImportError: - import simplejson as json +import json import os import pprint import tempfile diff --git a/atest/robot/running/nolog.py b/atest/robot/running/nolog.py index 906e22c33f0..36efd783369 100644 --- a/atest/robot/running/nolog.py +++ b/atest/robot/running/nolog.py @@ -1,8 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from __future__ import with_statement - def difference_between_stuff(file1, file2): with open(file1) as f1: content1 = f1.readlines() diff --git a/atest/run_atests.py b/atest/run_atests.py index 92933a01669..436c0cfe942 100755 --- a/atest/run_atests.py +++ b/atest/run_atests.py @@ -19,7 +19,6 @@ $ atest/run_atests.py python --test example atest/robot $ atest/run_atests.py /usr/bin/jython25 atest/robot/tags/tag_doc.txt """ -from __future__ import with_statement import re import os From dcbd31db096cd778c0e5c70f2107a96add6b76d4 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 9 Oct 2013 15:16:27 +0200 Subject: [PATCH 047/214] [python3] README update --HG-- extra : transplant_source : d%CFg%0A%F8%B6%BE%2C%F4%BEs%BA%D3%EC%A5%92%FE%27%A9%97 --- README.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.txt b/README.txt index d4745dd92b5..057dac194c7 100644 --- a/README.txt +++ b/README.txt @@ -1,6 +1,6 @@ -This is an unofficial Robot Framework Python 3 compatibility fork. +This is an unofficial Robot Framework Python 3.x compatibility fork. It also remains compatible with all officially supported -Python 2 platforms and versions, starting with 2.5. +Python 2.x platforms and versions, starting with 2.5. It uses the ``2to3`` tool in ``setup.py`` and ``atest/run_atests.py``. The latter copies ``src/robot/`` and ``atest/`` to ``atest/python3/`` @@ -20,7 +20,7 @@ You can also look at this URL for a complete diff: https://bitbucket.org/userzimmermann/robotframework-python3/compare/default..c148e32#diff Most of the acceptance tests are already passing with Python 3. -Only 109/3111 are currently failing on my machine, +Only ``72/3131`` critical tests are currently failing on my machine, but this is mostly related to the tests themselves, which need some further workarounds, switches and conversions. From a540fac7a9e86164d376813eb303d5e6bfc4d566 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 10 Oct 2013 18:06:45 +0200 Subject: [PATCH 048/214] [python3] atest: xml: Try comparing XML output with upper and lower case encoding names --HG-- extra : transplant_source : %E2UO%94%3B%2BF%80G%7F%B67%B5%7BR%F6%E48%8E%87 --- atest/testdata/standard_libraries/xml/resource.txt | 13 ++++++++++--- atest/testdata/standard_libraries/xml/save_xml.txt | 5 ++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/atest/testdata/standard_libraries/xml/resource.txt b/atest/testdata/standard_libraries/xml/resource.txt index a54156a69d1..ef1644336ce 100644 --- a/atest/testdata/standard_libraries/xml/resource.txt +++ b/atest/testdata/standard_libraries/xml/resource.txt @@ -28,13 +28,20 @@ Element Should Have Attributes ${expected} = Create Dictionary @{attributes} Dictionaries Should Be Equal ${elem.attrib} ${expected} +XML Should Be + [Arguments] ${xml} ${encoding} @{expected} + ${expected} = Catenate SEPARATOR=\n + ... @{expected} + Should Be Equal ${xml} ${expected} + Saved XML Should Be [Arguments] ${tree} @{expected} Save XML ${tree} ${OUTPUT} ${content} = Get File ${OUTPUT} - ${expected} = Catenate SEPARATOR=\n - ... @{expected} - Should Be Equal ${content} ${expected} + ${passed} = Run Keyword And Return Status + ... XML Should Be ${content} UTF-8 @{expected} + Run Keyword Unless ${passed} + ... XML Should Be ${content} utf-8 @{expected} Run Keyword Depending On Etree Version [Arguments] ${etree 1.3 keyword} ${etree 1.2 keyword}=No Operation diff --git a/atest/testdata/standard_libraries/xml/save_xml.txt b/atest/testdata/standard_libraries/xml/save_xml.txt index 00eadbb88cc..459cbd49064 100644 --- a/atest/testdata/standard_libraries/xml/save_xml.txt +++ b/atest/testdata/standard_libraries/xml/save_xml.txt @@ -50,4 +50,7 @@ Save Non-ASCII Using ASCII XML Content Should Be [Arguments] ${expected} ${encoding}=UTF-8 ${actual} = Get File ${OUTPUT} ${encoding} - Should Be Equal ${actual} \n${expected} + ${passed} = Run Keyword And Return Status + ... Should Be Equal ${actual} \n${expected} + Run Keyword Unless ${passed} + ... Should Be Equal ${actual} \n${expected} From e2ec79d7410548e6df6214c09518ae4946b8e7a2 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 14 Oct 2013 10:29:09 +0200 Subject: [PATCH 049/214] [python3] README update --HG-- extra : transplant_source : WCm%0CD%0Dj%BC%9D%8FTB%F0%5BA%CF%C9%82%AF%DD --- README.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.txt b/README.txt index 057dac194c7..e751f7dfe67 100644 --- a/README.txt +++ b/README.txt @@ -20,7 +20,7 @@ You can also look at this URL for a complete diff: https://bitbucket.org/userzimmermann/robotframework-python3/compare/default..c148e32#diff Most of the acceptance tests are already passing with Python 3. -Only ``72/3131`` critical tests are currently failing on my machine, +Only ``65/3131`` critical tests are currently failing on my machine, but this is mostly related to the tests themselves, which need some further workarounds, switches and conversions. From c51bf168f580765e043a37d9ff344ac070ef3c45 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 21 Oct 2013 15:57:56 +0200 Subject: [PATCH 050/214] [python3] run_atests: Fixed hex char conversion --HG-- extra : transplant_source : P%C3I%C1%7D%DB%3A%9A%FCy%ADU%8B%E8%11%DA%24N%1F%18 --- atest/run_atests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atest/run_atests.py b/atest/run_atests.py index 436c0cfe942..f35e82eeb66 100755 --- a/atest/run_atests.py +++ b/atest/run_atests.py @@ -91,7 +91,7 @@ text = re.sub( r'(.*)\\\\x([0-9a-f]{2})', lambda match: ( - chr(int(match.group(2), 16)) + (match.group(1) + chr(int(match.group(2), 16))) if not 'bytes' in match.group(1).lower() else match.group(0)), text) From f57f92c877cf1597a3868f204d5a413b6e32c647 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 21 Oct 2013 16:44:13 +0200 Subject: [PATCH 051/214] [python3] run_atests: More checks for hex char conversion --HG-- extra : transplant_source : %0A%EB%09%B5%B2%D8%5E%9E%14%A5hC%C4s%A8Cj%FC%13%8C --- atest/run_atests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/atest/run_atests.py b/atest/run_atests.py index f35e82eeb66..e7074429dc7 100755 --- a/atest/run_atests.py +++ b/atest/run_atests.py @@ -93,6 +93,7 @@ lambda match: ( (match.group(1) + chr(int(match.group(2), 16))) if not 'bytes' in match.group(1).lower() + and match.group(2) >= '80' else match.group(0)), text) text = re.sub( From 36c1e4993c45165846b99c32a6438d0af49bf41f Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 21 Oct 2013 16:44:40 +0200 Subject: [PATCH 052/214] [python3] README update --HG-- extra : transplant_source : %27H%CEy%3D%1F%0E%03%BEq%11%D31%A8%1E%83%9D%3Fg%03 --- README.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.txt b/README.txt index e751f7dfe67..403d181aedb 100644 --- a/README.txt +++ b/README.txt @@ -20,7 +20,7 @@ You can also look at this URL for a complete diff: https://bitbucket.org/userzimmermann/robotframework-python3/compare/default..c148e32#diff Most of the acceptance tests are already passing with Python 3. -Only ``65/3131`` critical tests are currently failing on my machine, +Only ``57/3132`` critical tests are currently failing on my machine, but this is mostly related to the tests themselves, which need some further workarounds, switches and conversions. From 1079798cb28227855a26df03f398aca269935ce1 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 21 Oct 2013 16:49:48 +0200 Subject: [PATCH 053/214] [python3] README: Updated fork diff url --HG-- extra : transplant_source : %82Uk%B2%F1%B1%21auI%AAX%5C%E3%C1%F9%5B%B8SW --- README.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.txt b/README.txt index 403d181aedb..022027ffe27 100644 --- a/README.txt +++ b/README.txt @@ -17,7 +17,7 @@ mostly use ``Run on python 2.x`` and ``3.x`` Keywords for switching. You can also look at this URL for a complete diff: -https://bitbucket.org/userzimmermann/robotframework-python3/compare/default..c148e32#diff +https://bitbucket.org/userzimmermann/robotframework-python3/compare/default..853a2e8#diff Most of the acceptance tests are already passing with Python 3. Only ``57/3132`` critical tests are currently failing on my machine, From 755996d73f57c6f8c91945247476fa5a131a460d Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 1 Nov 2013 09:52:03 +0000 Subject: [PATCH 054/214] [python3] Made utils.encode_to_system() Python 3 safe --HG-- extra : transplant_source : %D6%5B%CA%DEth%D7I%B7%FB%EB%CA%E2%E7%A3%EBr%9C%0E%A0 --- src/robot/libraries/Process.py | 2 -- src/robot/utils/encoding.py | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/robot/libraries/Process.py b/src/robot/libraries/Process.py index e6d5a744707..3d9ee293a37 100644 --- a/src/robot/libraries/Process.py +++ b/src/robot/libraries/Process.py @@ -696,6 +696,4 @@ def __str__(self): alias = %s env = %r""" % (self.cwd, self.stdout_stream, self.stderr_stream, self.shell, self.alias, self.env) - if sys.version_info[0] == 3: - return text return encode_to_system(text) diff --git a/src/robot/utils/encoding.py b/src/robot/utils/encoding.py index 96c3d317945..e94f89cccbe 100644 --- a/src/robot/utils/encoding.py +++ b/src/robot/utils/encoding.py @@ -48,4 +48,6 @@ def decode_from_system(string, can_be_from_java=True): def encode_to_system(string, errors='replace'): """Encodes Unicode to system encoding (e.g. cli args and env vars).""" + if sys.version_info[0] == 3: + return string return string.encode(SYSTEM_ENCODING, errors) From a468536b4e4584882e410f18abd370a2ecac6f11 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 4 Nov 2013 09:12:56 +0000 Subject: [PATCH 055/214] [python3] atest: process/sending_signal: Python 3 compatible prints --HG-- extra : transplant_source : %ED%AD%89%07%BC%AFZ%A1%02%16C%927%3D%B5S%B872%13 --- atest/testdata/standard_libraries/process/sending_signal.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atest/testdata/standard_libraries/process/sending_signal.txt b/atest/testdata/standard_libraries/process/sending_signal.txt index e3cb7753a50..a3eceba3279 100644 --- a/atest/testdata/standard_libraries/process/sending_signal.txt +++ b/atest/testdata/standard_libraries/process/sending_signal.txt @@ -51,9 +51,9 @@ Start Sleeping Process [Arguments] ${alias}= ${command} = Catenate SEPARATOR=\n ... import time - ... print 'start' + ... print('start') ... for i in range(25): time.sleep(0.1) - ... print 'end' + ... print('end') ${index} = Start Python Process ${command} alias=${alias} Sleep 0.1s [Return] ${index} From 1bdd44b32d09ffba42c7216dc576ddde54c43d02 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 4 Nov 2013 09:13:10 +0000 Subject: [PATCH 056/214] [python3] README update --HG-- extra : transplant_source : %9F%D8n%97W%BE%B3mC%E3%0Bd%97%C8%7F%E5s%B3qL --- README.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.txt b/README.txt index 022027ffe27..c0b538acea1 100644 --- a/README.txt +++ b/README.txt @@ -20,7 +20,7 @@ You can also look at this URL for a complete diff: https://bitbucket.org/userzimmermann/robotframework-python3/compare/default..853a2e8#diff Most of the acceptance tests are already passing with Python 3. -Only ``57/3132`` critical tests are currently failing on my machine, +Only ``59/3146`` critical tests are currently failing on my machine, but this is mostly related to the tests themselves, which need some further workarounds, switches and conversions. From 714e36296735a09e0a3ad8af9c02288f25ec1fdb Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 26 Nov 2013 15:33:59 +0000 Subject: [PATCH 057/214] [python3] atest: get_process_result: ()s for print --HG-- extra : transplant_source : %F71%F0%F5%B4Ox%86%C0%1A%97u%B0%1C%BB%DB%9B%C7-L --- .../standard_libraries/process/get_process_result.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atest/testdata/standard_libraries/process/get_process_result.txt b/atest/testdata/standard_libraries/process/get_process_result.txt index 4223038863d..68dbaec143b 100644 --- a/atest/testdata/standard_libraries/process/get_process_result.txt +++ b/atest/testdata/standard_libraries/process/get_process_result.txt @@ -44,7 +44,7 @@ Get same result multiple times Should Be Equal ${stderr} Framework Get result of active process - Start Python Process print 'Robot Framework' + Start Python Process print('Robot Framework') Wait For Process ${result} = Get Process Result ${stdout} = Get Process Result stdout=true @@ -54,7 +54,7 @@ Get result of active process Getting results of unfinished processes is not supported [Documentation] FAIL Getting results of unfinished processes is not supported. - Start Python Process print 'Robot Framework' + Start Python Process print('Robot Framework') Get Process Result *** Keywords *** From 4586c41a6564679d37335216898558fb1e9cdbcb Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 26 Nov 2013 16:14:14 +0000 Subject: [PATCH 058/214] [python3] atest: process_library: More ()s for prints --HG-- extra : transplant_source : .MC%5CK%11%82%01%D35%9F%8AS.%99%3BQG%BC%AB --- atest/testdata/standard_libraries/process/process_library.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atest/testdata/standard_libraries/process/process_library.txt b/atest/testdata/standard_libraries/process/process_library.txt index 110dc557add..1f23e02f613 100644 --- a/atest/testdata/standard_libraries/process/process_library.txt +++ b/atest/testdata/standard_libraries/process/process_library.txt @@ -97,7 +97,7 @@ Escaping equals sign Running a process in a shell ${result}= Run Process python -c "print('hello')" shell=True Result should equal ${result} stdout=hello - ${result}= Run Process python -c "print 'hello'" shell=0 + ${result}= Run Process python -c "print('hello')" shell=0 Result should equal ${result} stdout=hello Run Keyword And Expect Error * Run Process python -c "print('hello')" shell=${False} Run Keyword And Expect Error * Run Process python -c "print('hello')" shell=${0} @@ -166,7 +166,7 @@ Current working directory should not be used with stdout and stderr when absolut Lot of output [Tags] performance ${stdout}= Normalize Path %{TEMPDIR}/stdout.txt - ${handle}= Run Process python -c "for i in range(350000): \tprint 'a'*400" shell=True stdout=${stdout} stderr=STDOUT + ${handle}= Run Process python -c "for i in range(350000): \tprint('a'*400)" shell=True stdout=${stdout} stderr=STDOUT File Should Not Be Empty ${stdout} [Teardown] Safe Remove File ${stdout} From a5a054d02ce8f84dcfca3804eca8949cbe3e0047 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 26 Nov 2013 16:15:33 +0000 Subject: [PATCH 059/214] [python3] README: Failing critical atests: 57/3164 --HG-- extra : transplant_source : %28%C8%BF%03%26%9E%2C0H%E3%BA%14%9A%C0%86%AC8o%02%0A --- README.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.txt b/README.txt index 5ac6d15aac8..998225ce05d 100644 --- a/README.txt +++ b/README.txt @@ -20,7 +20,7 @@ You can also look at this URL for a complete diff: https://bitbucket.org/userzimmermann/robotframework-python3/compare/default..853a2e8#diff Most of the acceptance tests are already passing with Python 3. -Only ``59/3164`` critical tests are currently failing on my machine, +Only ``57/3164`` critical tests are currently failing on my machine, but this is mostly related to the tests themselves, which need some further workarounds, switches and conversions. From 7cfd156abc4134f7a070344853986d4c798b9b1c Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 27 Nov 2013 10:06:41 +0000 Subject: [PATCH 060/214] [python3] Made Convert To Bytes and related tests Python 3 compatible --HG-- extra : transplant_source : id%7EjS%3B%F0%BA%D7%D5%A2%CE%5E%19c%9ANA%E6%E8 --- .../testdata/standard_libraries/builtin/convert_to_bytes.txt | 4 +++- src/robot/libraries/BuiltIn.py | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/atest/testdata/standard_libraries/builtin/convert_to_bytes.txt b/atest/testdata/standard_libraries/builtin/convert_to_bytes.txt index 9ce91055c40..f08234392f8 100644 --- a/atest/testdata/standard_libraries/builtin/convert_to_bytes.txt +++ b/atest/testdata/standard_libraries/builtin/convert_to_bytes.txt @@ -153,7 +153,9 @@ Correct bytes should be created Bytes should be equal to [Arguments] ${bytes} ${expected} - ${expected} = Evaluate ''.join(chr(int(i)) for i in [${expected}]) + ${python3} = Evaluate __import__('sys').version_info[0] == 3 + ${expected} = Run Keyword If ${python3} Evaluate bytes([${expected}]) + ... ELSE Evaluate ''.join(chr(int(i)) for i in [${expected}]) Should Be Equal ${bytes} ${expected} Should Be Byte String ${bytes} diff --git a/src/robot/libraries/BuiltIn.py b/src/robot/libraries/BuiltIn.py index f5bd2b3528c..083ae4bd31e 100644 --- a/src/robot/libraries/BuiltIn.py +++ b/src/robot/libraries/BuiltIn.py @@ -350,6 +350,8 @@ def convert_to_bytes(self, input, input_type='text'): ordinals = getattr(self, '_get_ordinals_from_%s' % input_type) except AttributeError: raise RuntimeError("Invalid input type '%s'." % input_type) + if sys.version_info[0] == 3: + return bytes(ordinals(input)) return ''.join(chr(o) for o in ordinals(input)) except: raise RuntimeError("Creating bytes failed: %s" From db9eedaa02f4e4e61fb3e62645b63d6ecf223807 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 27 Nov 2013 10:14:03 +0000 Subject: [PATCH 061/214] [python3] Removed Create Bytes in favor of Convert To Bytes --HG-- extra : transplant_source : kr%C4%0F%1D%AC%ACp%7C%7D%9C%CC%F8vwlc%22%10%40 --- .../standard_libraries/string/encode_decode.txt | 12 ++++++------ src/robot/libraries/BuiltIn.py | 16 ---------------- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/atest/testdata/standard_libraries/string/encode_decode.txt b/atest/testdata/standard_libraries/string/encode_decode.txt index 5c505b0eb7b..759fd96f637 100644 --- a/atest/testdata/standard_libraries/string/encode_decode.txt +++ b/atest/testdata/standard_libraries/string/encode_decode.txt @@ -13,9 +13,9 @@ Encode ASCII String To Bytes Encode Non-ASCII String To Bytes ${bytes} = Encode String To Bytes Hyvä ISO-8859-1 - Byte Strings Should Be Equal ${bytes} Hyv\\xe4 + Byte Strings Should Be Equal ${bytes} Hyv\xe4 ${bytes} = Encode String To Bytes Hyvä UTF-8 strict - Byte Strings Should Be Equal ${bytes} Hyv\\xc3\\xa4 + Byte Strings Should Be Equal ${bytes} Hyv\xc3\xa4 Encode Non-ASCII String To Bytes Using Incompatible Encoding [Documentation] FAIL STARTS: UnicodeEncodeError: @@ -28,7 +28,7 @@ Encode Non-ASCII String To Bytes Using Incompatible Encoding And Error Handler Byte Strings Should Be Equal ${bytes} Hyv? Decode ASCII Bytes To String - ${bytes} = Create Bytes Hello, world! + ${bytes} = Convert To Bytes Hello, world! ${string} = Decode Bytes To String ${bytes} UTF-8 Should Be Equal ${string} Hello, world! @@ -51,13 +51,13 @@ Decode Non-ASCII Bytes To String Using Incompatible Encoding And Error Handler *** Keywords *** Create Byte String Variables - ${ISO-8859-1} = Create Bytes Hyv\\xe4 - ${UTF-8} = Create Bytes Hyv\\xc3\\xa4 + ${ISO-8859-1} = Convert To Bytes Hyv\xe4 + ${UTF-8} = Convert To Bytes Hyv\xc3\xa4 Set Suite Variable ${ISO-8859-1} Set Suite Variable ${UTF-8} Byte Strings Should Be Equal [Arguments] ${bytes} ${expected} Should Be Byte String ${bytes} - ${expected} = Create Bytes ${expected} + ${expected} = Convert To Bytes ${expected} Should Be Equal ${bytes} ${expected} diff --git a/src/robot/libraries/BuiltIn.py b/src/robot/libraries/BuiltIn.py index 083ae4bd31e..23ce2e5a464 100644 --- a/src/robot/libraries/BuiltIn.py +++ b/src/robot/libraries/BuiltIn.py @@ -407,22 +407,6 @@ def create_list(self, *items): """ return list(items) - def create_bytes(self, string): - """Creates a bytes object from `string` by evaluating 'b"%(string)s"'. - - Use two backslashes for writing bytes in hex: \\\\xXX - """ - try: - if type(string) is bytes: - return string - except NameError: - pass - string = string.replace('"', '\\"') - try: - return eval('b"%s"' % string) - except SyntaxError: # Python 2.5 - return eval('"%s"' % string) - class _Verify: From 21be4b136a351c90d8da9d00b4e2a6c341074ae5 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 27 Nov 2013 11:01:05 +0000 Subject: [PATCH 062/214] [python3] Little correction of latest merge. --HG-- extra : transplant_source : %26%0FS%04%FF%0E%91%AB%C6%0ECRX%C7D%17%21%B6%2C%88 --- src/robot/parsing/tsvreader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/robot/parsing/tsvreader.py b/src/robot/parsing/tsvreader.py index 137a83b8d97..27093a4a09c 100644 --- a/src/robot/parsing/tsvreader.py +++ b/src/robot/parsing/tsvreader.py @@ -35,7 +35,7 @@ def read(self, tsvfile, populator): def _process_row(self, row): if NBSP in row: row = row.replace(NBSP, ' ') - return row + return row.rstrip() @classmethod def split_row(cls, row): From f0f571e1705cab29e700c1e5cab79f39d5bffdcf Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 27 Nov 2013 15:33:35 +0000 Subject: [PATCH 063/214] [python3] testdoc: Little hack to prevent 2to3 from converting import pythonpathsetter. --HG-- extra : transplant_source : %99vqpf%22%0BmR%96A%FD%14%93%28%B8%60%3E%B8%24 --- src/robot/testdoc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/robot/testdoc.py b/src/robot/testdoc.py index 9f26b958e7f..9944dfd072e 100755 --- a/src/robot/testdoc.py +++ b/src/robot/testdoc.py @@ -85,7 +85,9 @@ # Allows running as a script. __name__ check needed with multiprocessing: # http://code.google.com/p/robotframework/issues/detail?id=1137 if 'robot' not in sys.modules and __name__ == '__main__': - import pythonpathsetter + ## import pythonpathsetter + #HACK: Prevent 2to3 from converting to relative import + pythonpathsetter = __import__('pythonpathsetter') from robot import utils from robot.conf import RobotSettings From 3d67d3e9fa409c9034980c739893d374043dc0fd Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 27 Nov 2013 15:35:17 +0000 Subject: [PATCH 064/214] [python3] run_atests: Simplified \\x.. conversions. --HG-- extra : transplant_source : %02%98%C2%A2X%A0%A2y%9E9c%00%B8%C6%5B%19%16%AC%81%DA --- atest/run_atests.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/atest/run_atests.py b/atest/run_atests.py index e7074429dc7..300ed742129 100755 --- a/atest/run_atests.py +++ b/atest/run_atests.py @@ -89,11 +89,10 @@ # with actual unicode characters, # if not used to create bytes objects: text = re.sub( - r'(.*)\\\\x([0-9a-f]{2})', + r'\\\\x([0-9a-f]{2})', lambda match: ( - (match.group(1) + chr(int(match.group(2), 16))) - if not 'bytes' in match.group(1).lower() - and match.group(2) >= '80' + chr(int(match.group(1), 16)) + if match.group(1) >= '80' else match.group(0)), text) text = re.sub( From f248c8842e6c0355421425bd58ded32f2b4ff528 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 27 Nov 2013 15:36:45 +0000 Subject: [PATCH 065/214] [python3] atest: collections/list: Workarounds for xrange and sorting of mixed str/int. --HG-- extra : transplant_source : %1B%8Bx%DD%B9-%7D%DC%05%BD%0DqU%C3%A6s%14O%B5%81 --- atest/testdata/standard_libraries/collections/list.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/atest/testdata/standard_libraries/collections/list.txt b/atest/testdata/standard_libraries/collections/list.txt index bbb6d46360e..0c4d95992ae 100644 --- a/atest/testdata/standard_libraries/collections/list.txt +++ b/atest/testdata/standard_libraries/collections/list.txt @@ -143,8 +143,8 @@ Reserve List Compare To Expected String ${LONG} [2, '1', '44', '43', 42, '41', 2, '1', '1'] Sort List - Sort List ${LONG} - Compare To Expected String ${LONG} [ 2, 2, 42, '1', '1' , '1', '41', '43', '44'] + Sort List ${STRINGS} + Compare To Expected String ${STRINGS} ['1', '1' , '1', '41', '43', '44'] Get From List ${value} = Get From List ${L4} 1 @@ -205,7 +205,7 @@ List Should Not Contain Value, Value Found And Own Error Message List Should Not Contain Value ${L1} 1 My error message! List Should Not Contain Duplicates With No Duplicates - ${iterable} ${tuple} = Evaluate xrange(100), (0, 1, 2, '0', '1', '2') + ${iterable} ${tuple} = Evaluate iter(range(100)), (0, 1, 2, '0', '1', '2') : FOR ${list} IN ${L0} ${L1} ${L2} ${L3} ${L4} ... ${iterable} ${tuple} \ List Should Not Contain Duplicates ${list} @@ -324,6 +324,9 @@ Create Lists For The Tests Set Test Variable \${L4} ${LONG} = Combine Lists ${L1} ${L2} ${L4} ${L2} Set Test Variable \${LONG} + ${STRINGS} = Evaluate + ... [item for item in ${LONG} if type(item) is not int] + Set Test Variable \${STRINGS} Insert Into List And Compare [Arguments] ${list} ${index} ${value} ${expected} From ab3f7167c39dea6ea7f86a48fb7ff9e03198f7f4 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 27 Nov 2013 15:47:32 +0000 Subject: [PATCH 066/214] [python3] Get Dictionary Keys: Don't sort in Python 3. --HG-- extra : transplant_source : y%07%12z%3F%ACk.Y%96M%88%2BB%16%C5739%D9 --- src/robot/libraries/Collections.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/robot/libraries/Collections.py b/src/robot/libraries/Collections.py index 12b2646990f..7e0ea628636 100644 --- a/src/robot/libraries/Collections.py +++ b/src/robot/libraries/Collections.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys + from robot.api import logger from robot.utils import plural_or_not, seq2str, seq2str2, unic from robot.utils.asserts import assert_equals @@ -519,6 +521,11 @@ def get_dictionary_keys(self, dictionary): => - ${keys} = ['a', 'b', 'c'] """ + #TODO: Sorting causes problems when key types are not comparable, + # especially in Python 3 where even basic types like int and str + # are not comparable to each other. + if sys.version_info[0] == 3: + return list(dictionary) return sorted(dictionary) def get_dictionary_values(self, dictionary): From ad198d418efa3b0457ae2b78fbe4a0e075033f83 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 27 Nov 2013 15:49:01 +0000 Subject: [PATCH 067/214] [python3] Grep File: Open file in binary mode. --HG-- extra : transplant_source : %23%95%3A%90%85%9F%B6D%9B2%16J4%F5%29%1Eu%80%21%3B --- src/robot/libraries/OperatingSystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/robot/libraries/OperatingSystem.py b/src/robot/libraries/OperatingSystem.py index a8a4dea4250..7ef6a8317b9 100644 --- a/src/robot/libraries/OperatingSystem.py +++ b/src/robot/libraries/OperatingSystem.py @@ -366,7 +366,7 @@ def grep_file(self, path, pattern, encoding='UTF-8'): lines = [] total_lines = 0 self._link("Reading file '%s'", path) - with open(path, 'rU') as f: + with open(path, 'rbU') as f: for line in f: total_lines += 1 line = unicode(line, encoding).rstrip('\n') From 54161e75bd495ecda043b2c1dd606c0bb6769c9a Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 27 Nov 2013 15:50:34 +0000 Subject: [PATCH 068/214] [python3] atest: process/resource: Use input() instead of raw_input() in Python 3. --HG-- extra : transplant_source : %5Em%CE%AB%CCC%88%0A%FB%9Cj%3F%D36%24%8C%85%DF%EEM --- atest/testdata/standard_libraries/process/resource.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/atest/testdata/standard_libraries/process/resource.txt b/atest/testdata/standard_libraries/process/resource.txt index e3cafb18e6b..97eaa0a214d 100644 --- a/atest/testdata/standard_libraries/process/resource.txt +++ b/atest/testdata/standard_libraries/process/resource.txt @@ -5,7 +5,9 @@ Library OperatingSystem *** Keywords *** Some process [Arguments] ${alias}=${null} - ${handle}= Start Python Process raw_input() alias=${alias} + ${handle}= Start Python Process + ... __import__('sys').version_info[0] == 3 and input() or raw_input() + ... alias=${alias} Process should be running [Return] ${handle} From 23b654134fdc48dc4f5e95aff9ad9ebdbbd9d10d Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 27 Nov 2013 15:53:16 +0000 Subject: [PATCH 069/214] [python3] atest: variables/builtin_variables: No L suffix in Python 3. --HG-- extra : transplant_source : %0Dc%14o%F9%D5g/%7E%28%7B%BA%CA%25%5D%DC%DA%CA%B4%95 --- atest/testdata/variables/builtin_variables.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atest/testdata/variables/builtin_variables.txt b/atest/testdata/variables/builtin_variables.txt index af5cbe4784f..3c6ae9ada3f 100644 --- a/atest/testdata/variables/builtin_variables.txt +++ b/atest/testdata/variables/builtin_variables.txt @@ -17,7 +17,8 @@ Integer Variables Should Be Equal ${1} ${one} Should Be Equal ${ - 2 } ${minus_two} Should Be True repr(${12345}) == '12345' - Should Be True repr(${123456789012345678901234567890}).endswith('L') + Should Be True + ... repr(${123456789012345678901234567890}).endswith('L') == (__import__('sys').version_info[0] < 3) Log No automatic hex conversion ${FF} Integer Variables With Base From 852fbb918a2ef6983a46acfba59f87a37af41fbc Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 27 Nov 2013 15:54:10 +0000 Subject: [PATCH 070/214] [python3] atest: More ()s for print. --HG-- extra : transplant_source : %22%83J%E1%ED%FF%D2%8E%80%1A%8E%C0%A1J%B4/%23ib%F4 --- atest/testdata/standard_libraries/process/wait_for_process.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atest/testdata/standard_libraries/process/wait_for_process.txt b/atest/testdata/standard_libraries/process/wait_for_process.txt index e30aee1fdd5..7de5417e290 100644 --- a/atest/testdata/standard_libraries/process/wait_for_process.txt +++ b/atest/testdata/standard_libraries/process/wait_for_process.txt @@ -6,7 +6,7 @@ Resource resource.txt *** Test Cases *** Wait For Process - ${process} = Start Python Process print 'Robot Framework' + ${process} = Start Python Process print('Robot Framework') ${result} = Wait For Process ${process} Process Should Be Stopped ${process} Should Be Equal As Integers ${result.rc} 0 From 2c100bdc53eb5e2f13da16ebab009e56dfe153b7 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 27 Nov 2013 15:55:07 +0000 Subject: [PATCH 071/214] [python3] atest: More explicit Convert To Bytes usage. --HG-- extra : transplant_source : %C23%1DD_%0Fv%A4%BB%8C%5C%9E%ADX%9C%F96%B5I%BB --- .../operating_system/get_file.txt | 3 ++- .../standard_libraries/string/should_be.txt | 24 ++++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/atest/testdata/standard_libraries/operating_system/get_file.txt b/atest/testdata/standard_libraries/operating_system/get_file.txt index dc08f595edc..8ed24897ce8 100644 --- a/atest/testdata/standard_libraries/operating_system/get_file.txt +++ b/atest/testdata/standard_libraries/operating_system/get_file.txt @@ -30,7 +30,8 @@ Get File With Space In Name Get Binary File Create File ${TESTFILE} hello world\r\nbinary ${file}= Get Binary File ${TESTFILE} - Should Be Equal ${file} hello world\r\nbinary + ${expected}= Convert To Bytes hello world\r\nbinary + Should Be Equal ${file} ${expected} Log File Create File ${TESTFILE} hello world\nwith two lines diff --git a/atest/testdata/standard_libraries/string/should_be.txt b/atest/testdata/standard_libraries/string/should_be.txt index ecbd2d300fc..0ed73cb7745 100644 --- a/atest/testdata/standard_libraries/string/should_be.txt +++ b/atest/testdata/standard_libraries/string/should_be.txt @@ -1,6 +1,6 @@ *** Settings *** Library String -Suite Setup Create Byte String Variables +Suite Setup Create Bytes and String Variables *** Variables *** ${BYTES} @@ -9,7 +9,7 @@ ${BYTES} Should Be String Positive Should be String Robot Should be String ${EMPTY} - Should be String ${BYTES} + Should be String ${STRING} Should Be String Negative [Template] Run Keyword And Expect Error @@ -22,7 +22,7 @@ Should Not Be String Positive Should Not Be String Negative [Template] Run Keyword And Expect Error - '${BYTES}' is a string. Should not be string ${BYTES} + '${STRING}' is a string. Should not be string ${STRING} My error message Should not be string Hello My error message Should Be Unicode String Positive @@ -43,32 +43,34 @@ Should Be Byte String Negative Should Be Lowercase Positive Should Be Lowercase foo bar - Should Be Lowercase ${BYTES.lower()} + Should Be Lowercase ${STRING.lower()} Should Be Lowercase Negative [Template] Run Keyword And Expect Error - '${BYTES}' is not lowercase. Should Be Lowercase ${BYTES} + '${STRING}' is not lowercase. Should Be Lowercase ${STRING} My error Should Be Lowercase UP! My error Should Be Uppercase Positive Should Be Uppercase FOO BAR - Should Be Uppercase ${BYTES.upper()} + Should Be Uppercase ${STRING.upper()} Should Be Uppercase Negative [Template] Run Keyword And Expect Error - '${BYTES}' is not uppercase. Should Be Uppercase ${BYTES} + '${STRING}' is not uppercase. Should Be Uppercase ${STRING} Custom error Should Be Uppercase low... Custom error Should Be Titlecase Positive Should Be Titlecase Foo Bar! - Should Be Titlecase ${BYTES} + Should Be Titlecase ${STRING} Should Be Titlecase Negative [Template] Run Keyword And Expect Error - '${BYTES.lower()}' is not titlecase. Should Be Titlecase ${BYTES.lower()} + '${STRING.lower()}' is not titlecase. Should Be Titlecase ${STRING.lower()} Special error Should Be Titlecase all low Special error *** Keywords *** -Create Byte String Variables - ${BYTES} = Evaluate "Hyv\\xe4" +Create Bytes and String Variables + ${STRING} = Evaluate "Hyv\\xe4" + Set Suite Variable ${STRING} + ${BYTES} = Convert To Bytes ${STRING} Set Suite Variable ${BYTES} From 74d63f36681511cacedf80ce45946736f7f492ca Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 27 Nov 2013 15:56:25 +0000 Subject: [PATCH 072/214] [python3] atest: More compatibility in exception msg checks. --HG-- extra : transplant_source : Zk7t%EE%3B%60RhUw%EF%D4%0E%D6M%C04%D1%B7 --- atest/testdata/standard_libraries/process/process_library.txt | 2 +- atest/testdata/standard_libraries/xml/parsing.txt | 2 +- atest/testdata/standard_libraries/xml/save_xml.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/atest/testdata/standard_libraries/process/process_library.txt b/atest/testdata/standard_libraries/process/process_library.txt index 1227f80d4cc..61cd29e3af4 100644 --- a/atest/testdata/standard_libraries/process/process_library.txt +++ b/atest/testdata/standard_libraries/process/process_library.txt @@ -15,7 +15,7 @@ Running a process Error in exit code and stderr output ${result}= Run Python Process 1/0 - Result should match ${result} stderr=*ZeroDivisionError: integer division or modulo by zero* rc=1 + Result should match ${result} stderr=*ZeroDivisionError: *division* by zero* rc=1 Start And Wait Process ${handle}= Start Python Process import time;time.sleep(0.1) diff --git a/atest/testdata/standard_libraries/xml/parsing.txt b/atest/testdata/standard_libraries/xml/parsing.txt index 7a6dc3d6e67..bd49b355b20 100644 --- a/atest/testdata/standard_libraries/xml/parsing.txt +++ b/atest/testdata/standard_libraries/xml/parsing.txt @@ -24,5 +24,5 @@ Parse invalid string Parse XML urho Parse non-existing file - [Documentation] FAIL STARTS: IOError: + [Documentation] FAIL REGEXP: (IO|FileNotFound)Error: .* Parse XML non-existing.xml diff --git a/atest/testdata/standard_libraries/xml/save_xml.txt b/atest/testdata/standard_libraries/xml/save_xml.txt index 459cbd49064..84c4ba7052c 100644 --- a/atest/testdata/standard_libraries/xml/save_xml.txt +++ b/atest/testdata/standard_libraries/xml/save_xml.txt @@ -33,7 +33,7 @@ Save Non-ASCII XML Using Custom Encoding XML Content Should Be ${NON-ASCII} iso-8859-1 Save to Invalid File - [Documentation] FAIL STARTS: IOError: + [Documentation] FAIL REGEXP: (IO|IsADirectory)Error: .* Save XML ${SIMPLE} %{TEMPDIR} Save Using Invalid Encoding From d9f5f259a458c62a08a61f95402603a74ef059f2 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 27 Nov 2013 15:58:09 +0000 Subject: [PATCH 073/214] [python3] atest: builtin/log: str() of a class is different in Python 3. --HG-- extra : transplant_source : %3Bl%1A%16%80x%93%D7%87%B3%9B%0CT%F6%03%ED%AD%7B%ED%A9 --- atest/robot/standard_libraries/builtin/log.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/atest/robot/standard_libraries/builtin/log.txt b/atest/robot/standard_libraries/builtin/log.txt index ba139418303..fb6957f80f1 100644 --- a/atest/robot/standard_libraries/builtin/log.txt +++ b/atest/robot/standard_libraries/builtin/log.txt @@ -61,7 +61,10 @@ Log repr Log callable ${tc} = Check Test Case ${TEST NAME} - Check Log Message ${tc.kws[0].msgs[0]} objects_for_call_method.MyObject + Run on python 3.x + ... Check Log Message ${tc.kws[0].msgs[0]} + Run on python 2.x + ... Check Log Message ${tc.kws[0].msgs[0]} objects_for_call_method.MyObject Log Many ${tc} = Check Test Case ${TEST NAME} From e645f07b2511becd034bcceee2b218a34c66d42e Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 27 Nov 2013 16:01:06 +0000 Subject: [PATCH 074/214] [python3] README: Failing critical atests: 36/3249 --HG-- extra : transplant_source : %AE%EA%19%F5g%F0e%82%961%1D%F1%E5a%7D%2C%B0%2B%BFi --- README.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.txt b/README.txt index 998225ce05d..9f19b933090 100644 --- a/README.txt +++ b/README.txt @@ -20,7 +20,7 @@ You can also look at this URL for a complete diff: https://bitbucket.org/userzimmermann/robotframework-python3/compare/default..853a2e8#diff Most of the acceptance tests are already passing with Python 3. -Only ``57/3164`` critical tests are currently failing on my machine, +Only ``36/3249`` critical tests are currently failing on my machine, but this is mostly related to the tests themselves, which need some further workarounds, switches and conversions. From ca198b59d61b3f19e5be6b82545efc6c1bdaf884 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 29 Nov 2013 09:30:17 +0000 Subject: [PATCH 075/214] [python3] atest: keywords/trace_log_return_value: Little workaround. --HG-- extra : transplant_source : %90%D9%C3b%93%AFJ%8C%22%CA%D8%82%C9%A9%7D%23%A4%B9M%FC --- atest/robot/keywords/trace_log_return_value.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atest/robot/keywords/trace_log_return_value.txt b/atest/robot/keywords/trace_log_return_value.txt index 06b467dab3b..1f3693af199 100644 --- a/atest/robot/keywords/trace_log_return_value.txt +++ b/atest/robot/keywords/trace_log_return_value.txt @@ -39,7 +39,8 @@ Return Object with Invalid Unicode Repr [Documentation] How the return value is logged depends on the interpreter. ${test} = Check Test Case ${TESTNAME} ${path} ${base} = Split Path ${INTERPRETER} - ${ret} = Set Variable If 'python' in '${base}' u'Hyv\\xe4' Hyvä + ${ret} = Set Variable If 'python' in '${base}' and not '${PYTHON3}' + ... u'Hyv\\xe4' Hyvä Check Log Message ${test.kws[0].msgs[1]} Return: ${ret} TRACE Return Object with Non Ascii String from Repr From cac1d141cabc6f89098028e8f87705db66eee92e Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 29 Nov 2013 09:32:02 +0000 Subject: [PATCH 076/214] [python3] atest: LibUsingPyLogging.Message: Made __str__() return __unicode__() in Python 3. --HG-- extra : transplant_source : %60%2B%D4X%5E%C7%FB%AB%91%91%DB%0D%B1pB%18A%E2%EFl --- atest/testdata/test_libraries/LibUsingPyLogging.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/atest/testdata/test_libraries/LibUsingPyLogging.py b/atest/testdata/test_libraries/LibUsingPyLogging.py index 622b084bcf8..daeeb57a6aa 100644 --- a/atest/testdata/test_libraries/LibUsingPyLogging.py +++ b/atest/testdata/test_libraries/LibUsingPyLogging.py @@ -23,6 +23,8 @@ def __init__(self, msg=''): def __unicode__(self): return self.msg def __str__(self): + if sys.version_info[0] == 3: + return self.__unicode__() return unicode(self).encode('UTF-8') def __repr__(self): return repr(str(self)) From 0ea15034f384a5260f0e12226cf0022527d14b42 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 29 Nov 2013 09:34:44 +0000 Subject: [PATCH 077/214] [python3] atest: ExampleLibrary: Made .print_() and .exception() Python 3 compatible. --HG-- extra : transplant_source : %BB%DC%C3%0A%0A%E3%8B%89%7Djk%3F%7Cp%923%F0ew%ED --- atest/testresources/testlibs/ExampleLibrary.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/atest/testresources/testlibs/ExampleLibrary.py b/atest/testresources/testlibs/ExampleLibrary.py index aa374582855..dc3240ddf27 100644 --- a/atest/testresources/testlibs/ExampleLibrary.py +++ b/atest/testresources/testlibs/ExampleLibrary.py @@ -15,7 +15,7 @@ class ExampleLibrary: def print_(self, msg, stream='stdout'): """Print given message to selected stream (stdout or stderr)""" out_stream = getattr(sys, stream) - out_stream.write(msg) + out_stream.write(unicode(msg)) def print_n_times(self, msg, count, delay=0): """Print given message n times""" @@ -54,6 +54,8 @@ def multi_line_doc(self): def exception(self, name, msg=""): """Raise exception with given name and message""" exception = getattr(exceptions, name) + if msg is None: + raise exception raise exception, msg def external_exception(self, name, msg): From d6283499e713d6e27c7f13429b19d48dd8e476e4 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 29 Nov 2013 11:09:47 +0000 Subject: [PATCH 078/214] [python3] OperatingSystem.Start Process: Removed textio= option in favor of default text mode streams. --HG-- extra : transplant_source : C%26%BD%BD%AC%CD3Ql%10%82%B6%A6%0E%D1%2A%15%80p%FF --- .../operating_system/start_process.txt | 42 +++++++++---------- .../process/start_process_preferences.txt | 4 +- src/robot/libraries/OperatingSystem.py | 13 ++---- 3 files changed, 27 insertions(+), 32 deletions(-) diff --git a/atest/testdata/standard_libraries/operating_system/start_process.txt b/atest/testdata/standard_libraries/operating_system/start_process.txt index 59f592dad15..33453ab6990 100644 --- a/atest/testdata/standard_libraries/operating_system/start_process.txt +++ b/atest/testdata/standard_libraries/operating_system/start_process.txt @@ -10,40 +10,40 @@ ${TEMP FILE} ${CURDIR}${/}robot-start-process.tmp *** Test Cases *** Start Process - ${index} = Start Process ${PROG} 0 hello textio=True + ${index} = Start Process ${PROG} 0 hello Equals ${index} ${2} ${out} = Read Process Output Equals ${out} hello - Start Process ${PROG} 0 hi textio=True + Start Process ${PROG} 0 hi ${out} = Read Process Output Equals ${out} hi Stderr Is Redirected To Stdout - Start Process ${PROG} 0 hey error textio=True + Start Process ${PROG} 0 hey error ${out} = Read Process Output Should Match Regexp ${out} ^(hey\nerror|error\nhey)$ It Should Be Possble To Start Background Process - Start Process ${PROG} 0 hey error & textio=True + Start Process ${PROG} 0 hey error & ${out} = Read Process Output Should Match Regexp ${out} ^(hey\nerror|error\nhey)$ Start Writable Process - Start Process ${WRITABLE_PROG} hello world textio=True + Start Process ${WRITABLE_PROG} hello world ${output} = Read Process Output Equals ${output} HELLO WORLD Cannot Read From A Stopped Process [Documentation] FAIL Cannot read from a closed process - Start Process ${PROG} 0 hello textio=True + Start Process ${PROG} 0 hello ${output} = Read Process Output ${output} = Read Process Output Switch Process - ${first} = Start Process ${PROG} 0 hello textio=True - ${second} = Start Process ${PROG} 0 world textio=True - Start Process ${WRITABLE_PROG} hello world alias textio=True - Start Process ${PROG} 0 olleh textio=True + ${first} = Start Process ${PROG} 0 hello + ${second} = Start Process ${PROG} 0 world + Start Process ${WRITABLE_PROG} hello world alias + Start Process ${PROG} 0 olleh ${output} = Read Process Output Equals ${output} olleh Switch Process ${first} @@ -58,10 +58,10 @@ Switch Process Lives Between Tests Setup [Documentation] Starts a process used in next test case - Start Process ${PROG} 0 from_test_case ${EMPTY} test case process textio=True + Start Process ${PROG} 0 from_test_case ${EMPTY} test case process Lives Between Tests - [Setup] Start Process ${PROG} 0 from_test_setup ${EMPTY} test setup process textio=True + [Setup] Start Process ${PROG} 0 from_test_setup ${EMPTY} test setup process Switch Process suite setup process ${output} = Read Process Output Equals ${output} from_suite_setup @@ -74,45 +74,45 @@ Lives Between Tests Stop All [Documentation] FAIL No active processes - ${index} = Start Process ${PROG} 0 hello textio=True + ${index} = Start Process ${PROG} 0 hello Start Process ${PROG} 0 hello Stop All Processes - ${index} = Start Process ${PROG} 0 hello textio=True + ${index} = Start Process ${PROG} 0 hello Equals ${index} ${1} Stop All Processes Read Process Output Stopping Already Stopped Processes Is OK - Start Process ${PROG} 0 hello textio=True + Start Process ${PROG} 0 hello ${output} = Read Process Output Stop Process Stop Process - Start Process ${PROG} 0 hello textio=True + Start Process ${PROG} 0 hello Stop Process Stop Process Redirecting Stdout To File - Start Process ${PROG} 0 hello > ${TEMP FILE} textio=True + Start Process ${PROG} 0 hello > ${TEMP FILE} Output and Temp File Should Be ${EMPTY} hello [Teardown] Remove File ${TEMP FILE} Redirecting Stderr To File - Start Process ${PROG} 0 hello world 2> ${TEMP FILE} textio=True + Start Process ${PROG} 0 hello world 2> ${TEMP FILE} Output and Temp File Should Be hello world [Teardown] Remove File ${TEMP FILE} Redirecting Stderr To Stdout - Start Process ${PROG} 0 hello world 2>&1 textio=True + Start Process ${PROG} 0 hello world 2>&1 Output Should Be ^(hello\nworld|world\nhello)$ Reading Output With Lot Of Data In Stdout And Stderr - Start Process ${PROG} 0 hello world 15000 textio=True + Start Process ${PROG} 0 hello world 15000 ${out} = Read Process Output Length Should Be ${out} ${12*15000-1} *** Keywords *** My Setup - ${index} = Start Process ${PROG} 0 from_suite_setup ${EMPTY} suite setup process textio=True + ${index} = Start Process ${PROG} 0 from_suite_setup ${EMPTY} suite setup process Equals ${index} ${1} Output And Temp File Should Be diff --git a/atest/testdata/standard_libraries/process/start_process_preferences.txt b/atest/testdata/standard_libraries/process/start_process_preferences.txt index 3edba846de0..52bcb3153ec 100644 --- a/atest/testdata/standard_libraries/process/start_process_preferences.txt +++ b/atest/testdata/standard_libraries/process/start_process_preferences.txt @@ -6,7 +6,7 @@ Resource resource.txt *** Test Cases *** Explicitly run Operating System library keyword - ${handle}= OperatingSystem.Start Process python -c "import os; print os.path.abspath(os.curdir);" textio=True + ${handle}= OperatingSystem.Start Process python -c "import os; print os.path.abspath(os.curdir);" ${out}= Read Process Output Explicitly run Process library keyword @@ -22,7 +22,7 @@ Implicitly run Process library keyword Implicitly run Operating System library keyword when library search order is set Set Library Search Order OperatingSystem - ${handle}= Start Process python -c "import os; print os.path.abspath(os.curdir);" textio=True + ${handle}= Start Process python -c "import os; print os.path.abspath(os.curdir);" ${out}= Read Process Output [Teardown] Set Library Search Order ${EMPTY} diff --git a/src/robot/libraries/OperatingSystem.py b/src/robot/libraries/OperatingSystem.py index 7ef6a8317b9..7147c989793 100644 --- a/src/robot/libraries/OperatingSystem.py +++ b/src/robot/libraries/OperatingSystem.py @@ -203,7 +203,7 @@ def _run(self, command): rc = process.close() return rc, stdout - def start_process(self, command, stdin=None, alias=None, textio=False): + def start_process(self, command, stdin=None, alias=None): """It is recommended to use same keyword from Process library instead. Starts the given command as a background process. @@ -230,10 +230,6 @@ def start_process(self, command, stdin=None, alias=None, textio=False): keyword, but redirecting is done when the process is started and not by adding '2>&1' to the command. - Setting `textio` to any non-false value, such as `textio=True`, - the command's input/output streams will be opened in text mode, - working with `str` instead of `bytes` in Python 3.x. - Example: | Start Process | /path/longlasting.sh | | Do Something | | @@ -241,7 +237,7 @@ def start_process(self, command, stdin=None, alias=None, textio=False): | Should Contain | ${output} | Expected text | | [Teardown] | Stop All Processes | """ - process = _Process2(command, stdin, textio=bool(textio)) + process = _Process2(command, stdin) self._info("Running command '%s'" % process) return PROCESSES.register(process, alias) @@ -1352,13 +1348,12 @@ def _process_output(self, stdout): class _Process2(_Process): - def __init__(self, command, input_, textio=False): + def __init__(self, command, input_): self._command = self._process_command(command) - ## raise RuntimeError(str(text_mode)) p = subprocess.Popen(self._command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=os.sep=='/', - universal_newlines=textio) + universal_newlines=True) stdin, self.stdout = p.stdin, p.stdout if input_: stdin.write(input_) From 64e9b9f4b25d13bf4aa742a5062579cdfbd165dc Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 29 Nov 2013 11:12:33 +0000 Subject: [PATCH 079/214] [python3] Process.Start Process: Added boolean bytesio= switch. --HG-- extra : transplant_source : %A8%98%CB%23%B4%12%EFBO%91%CE%02%FBQ%13%B8%92E%8A%8B --- src/robot/libraries/Process.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/robot/libraries/Process.py b/src/robot/libraries/Process.py index e64c3918588..e0a63b9579c 100644 --- a/src/robot/libraries/Process.py +++ b/src/robot/libraries/Process.py @@ -89,6 +89,7 @@ class Process(object): | env: | Overrides the named environment variable(s) only. | | stdout | Path of a file where to write standard output. | | stderr | Path of a file where to write standard error. | + | bytesio | Specifies that streams are opened in binary mode. | | alias | Alias given to the process. | == Running processes in shell == @@ -166,6 +167,15 @@ class Process(object): Note that the created output files are not automatically removed after the test run. The user is responsible to remove them if needed. + == Text or binary streams == + + The `bytesio` argument specifies that stdin, stdout and stderr streams + are opened in binary mode. By default they are opened in text mode. + + Starting with Python 3 text streams only work with strings, binary streams + only with bytes. If you want to send or receive bytes instead of strings, + give `bytesio` any non-false value, such as `bytesio=True`. + == Alias == A custom name given to the process that can be used when selecting the @@ -316,7 +326,7 @@ def start_process(self, command, *arguments, **configuration): shell=config.shell, cwd=config.cwd, env=config.env, - universal_newlines=True) + universal_newlines=not config.bytesio) self._results[process] = ExecutionResult(process, config.stdout_stream, config.stderr_stream) @@ -713,11 +723,12 @@ def __str__(self): class ProcessConfig(object): def __init__(self, cwd=None, shell=False, stdout=None, stderr=None, - alias=None, env=None, **rest): + bytesio=False, alias=None, env=None, **rest): self.cwd = self._get_cwd(cwd) self.stdout_stream = self._new_stream(stdout) self.stderr_stream = self._get_stderr(stderr, stdout) self.shell = is_true(shell) + self.bytesio = is_true(bytesio) self.alias = alias self.env = self._construct_env(env, rest) From 8a640df73c5dbfa1d22b08e40e03708aa7a98431 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 5 Dec 2013 08:58:43 +0000 Subject: [PATCH 080/214] [python3] Process: Renamed bytesio= to binary_mode= --HG-- extra : transplant_source : %B8%1A%F8%9D%0E%5E%93%CB%5D%23%A006%2C%24%25h%96N%0D --- src/robot/libraries/Process.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/robot/libraries/Process.py b/src/robot/libraries/Process.py index e0a63b9579c..0b83531f9fe 100644 --- a/src/robot/libraries/Process.py +++ b/src/robot/libraries/Process.py @@ -82,15 +82,15 @@ class Process(object): use syntax like `name=value`. Available configuration arguments are listed below and discussed further in sections afterwards. - | = Name = | = Explanation = | - | shell | Specifies whether to run the command in shell or not | - | cwd | Specifies the working directory. | - | env | Specifies environment variables given to the process. | - | env: | Overrides the named environment variable(s) only. | - | stdout | Path of a file where to write standard output. | - | stderr | Path of a file where to write standard error. | - | bytesio | Specifies that streams are opened in binary mode. | - | alias | Alias given to the process. | + | = Name = | = Explanation = | + | shell | Specifies whether to run the command in shell or not | + | cwd | Specifies the working directory. | + | env | Specifies environment variables given to the process. | + | env: | Overrides the named environment variable(s) only. | + | stdout | Path of a file where to write standard output. | + | stderr | Path of a file where to write standard error. | + | binary_mode | Specifies whether streams are opened in binary or text mode | + | alias | Alias given to the process. | == Running processes in shell == @@ -169,12 +169,12 @@ class Process(object): == Text or binary streams == - The `bytesio` argument specifies that stdin, stdout and stderr streams - are opened in binary mode. By default they are opened in text mode. + The `binary_mode` argument specifies whether stdin, stdout and stderr + are opened in binary or text mode. By default they are opened in text mode. Starting with Python 3 text streams only work with strings, binary streams only with bytes. If you want to send or receive bytes instead of strings, - give `bytesio` any non-false value, such as `bytesio=True`. + give `binary_mode` any non-false value, such as `binary_mode=True`. == Alias == @@ -326,7 +326,7 @@ def start_process(self, command, *arguments, **configuration): shell=config.shell, cwd=config.cwd, env=config.env, - universal_newlines=not config.bytesio) + universal_newlines=not config.binary_mode) self._results[process] = ExecutionResult(process, config.stdout_stream, config.stderr_stream) @@ -723,12 +723,12 @@ def __str__(self): class ProcessConfig(object): def __init__(self, cwd=None, shell=False, stdout=None, stderr=None, - bytesio=False, alias=None, env=None, **rest): + binary_mode=False, alias=None, env=None, **rest): self.cwd = self._get_cwd(cwd) self.stdout_stream = self._new_stream(stdout) self.stderr_stream = self._get_stderr(stderr, stdout) self.shell = is_true(shell) - self.bytesio = is_true(bytesio) + self.binary_mode = is_true(binary_mode) self.alias = alias self.env = self._construct_env(env, rest) From 52b95e15972c373aa124f55a7d1daf4eef257702 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 5 Dec 2013 08:59:21 +0000 Subject: [PATCH 081/214] [python3] run_atests.py: Some cleanup. --HG-- extra : transplant_source : %B8d%BB0%D9%DD%C4%94%F7%CE%9A%88%D8t%25%28%94%86w%E2 --- atest/run_atests.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/atest/run_atests.py b/atest/run_atests.py index 300ed742129..5bb747ad0e6 100755 --- a/atest/run_atests.py +++ b/atest/run_atests.py @@ -32,9 +32,9 @@ if sys.version_info < (2, 6): sys.exit('Running this script requires Python 2.6 or newer.') -try: - CURDIR = CURDIR -except NameError: +# Check for new working dir after 2to3: +if not 'CURDIR' in globals(): + # ==> still the original script before 2to3. CURDIR = dirname(abspath(__file__)) ROBOTDIR = join(CURDIR, '..', 'src', 'robot') @@ -43,10 +43,10 @@ # - Run 2to3 # - Modify Python literals in Suite/Resource .txt files # - Exec this file's copy in-place for actual testing -try: - # Is this file already the Python 3 copy or the original? - do2to3 = do2to3 -except NameError: + +# Is this script already the Python 3 copy? +if not 'do2to3' in globals(): + # ==> still the original. do2to3 = True if sys.version_info[0] == 3 and do2to3: PY3DIR = join(CURDIR, 'python3') From 3739e4a05ef0e40c8c4a93411bf1487ba3274a06 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 5 Dec 2013 09:37:10 +0000 Subject: [PATCH 082/214] [python3] atest: Again some more ()s for print. --HG-- extra : transplant_source : %8B%FA%5DNK%D8%D5%94O7%7B%84%3F_%E8%9Cf%06%9A%7E --- .../standard_libraries/process/terminate_and_pid.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/atest/testdata/standard_libraries/process/terminate_and_pid.txt b/atest/testdata/standard_libraries/process/terminate_and_pid.txt index 2b5443d4359..adee4c043b2 100644 --- a/atest/testdata/standard_libraries/process/terminate_and_pid.txt +++ b/atest/testdata/standard_libraries/process/terminate_and_pid.txt @@ -92,7 +92,7 @@ Pid Process Should Be Stopped ${handle} Getting PIDs in different ways should give same result - ${handle}= Start Process python -c "print 'hello'" shell=True alias=hello + ${handle}= Start Process python -c "print('hello')" shell=True alias=hello ${pid1}= Get Process Id ${pid2}= Get Process Id hello ${pid3}= Get Process Id ${handle} @@ -102,6 +102,6 @@ Getting PIDs in different ways should give same result Lot of output [Tags] performance ${stdout}= Normalize Path %{TEMPDIR}/stdout.txt - ${handle}= Run Process python -c "for i in range(350000): \tprint 'a'*400" shell=True stdout=${stdout} stderr=STDOUT + ${handle}= Run Process python -c "for i in range(350000): \tprint('a'*400)" shell=True stdout=${stdout} stderr=STDOUT File Should Not Be Empty ${stdout} - [Teardown] Safe Remove File ${stdout} \ No newline at end of file + [Teardown] Safe Remove File ${stdout} From 2a67a722e35f03b59d5e7d69c9e28dd8d0be8542 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 10 Dec 2013 10:48:00 +0000 Subject: [PATCH 083/214] [python3] Fixed PipeSeparatedTxtWriter._write_row() --HG-- extra : transplant_source : %AE%C1%EC%3BP%1Ep%91%D3%E6%DF%1F%8A%EB0%8C%83d%17%AD --- src/robot/writer/filewriters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/robot/writer/filewriters.py b/src/robot/writer/filewriters.py index 9a160f50ccc..06d89e95601 100644 --- a/src/robot/writer/filewriters.py +++ b/src/robot/writer/filewriters.py @@ -105,7 +105,7 @@ def _write_row(self, row): row = self._separator.join(row) if row: row = '| ' + row + ' |' - row += row + self._line_separator + row += self._line_separator encoded_row = self._encode(row) try: self._output.write(encoded_row) From 173f48ecd2c322f71c71393750bce36d6c361812 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 10 Dec 2013 11:31:50 +0000 Subject: [PATCH 084/214] [python3] BuiltIn.Convert To Bytes: Directly convert bytes and bytearray in Python 3 if input_type is text or int. --HG-- extra : transplant_source : %8B%22%95%D1%C0%A4%BB%B1u%3D%91.%F9P%C7%B5i%12%26%C0 --- src/robot/libraries/BuiltIn.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/robot/libraries/BuiltIn.py b/src/robot/libraries/BuiltIn.py index 23ce2e5a464..b7f83d5ed7d 100644 --- a/src/robot/libraries/BuiltIn.py +++ b/src/robot/libraries/BuiltIn.py @@ -346,6 +346,10 @@ def convert_to_bytes(self, input, input_type='text'): New in Robot Framework 2.8.2. """ try: + if sys.version_info[0] == 3 and input_type in ('text', 'int') and ( + isinstance(input, (bytes, bytearray)) + ): + return bytes(input) try: ordinals = getattr(self, '_get_ordinals_from_%s' % input_type) except AttributeError: From 1c202fac66515d3aaca050d8cf5f7f55330df158 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 10 Dec 2013 12:50:14 +0000 Subject: [PATCH 085/214] [python3] atest: cli/runner/debugfile: More regex for syslog checks. --HG-- extra : transplant_source : %D5%87%5B%82%07v%B5r%95%BB%AA%A1j%B6%F6%CC%DF%93%E8R --- atest/robot/cli/runner/debugfile.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atest/robot/cli/runner/debugfile.txt b/atest/robot/cli/runner/debugfile.txt index ffafc37fd0a..e97ae7a8386 100644 --- a/atest/robot/cli/runner/debugfile.txt +++ b/atest/robot/cli/runner/debugfile.txt @@ -24,7 +24,7 @@ Debugfile ... ${TIMESTAMP} - DEBUG - Logging with debug level ... ${TIMESTAMP} - INFO - +-- END KW: BuiltIn.Log Debug file should contain ${content} + END SUITE: Normal - Check Syslog Contains DebugFile: DeBug.TXT + Check Syslog Contains Regexp Debug(F| f)ile: .*DeBug.TXT ${path} = Set Variable [:.\\w /\\\\~+-]*DeBug\\.TXT Check Stdout Matches Regexp (?s).*Debug: {3}${path}.* Check Syslog Matches Regexp (?s).*Debug: ${path}.* @@ -45,7 +45,7 @@ Debugfile Log Level Should Always Be Debug No Debugfile Run Tests Without Processing Output --outputdir ${CLI OUTDIR} --debugfile NoNe -o o.xml ${TESTFILE} Directory Should Contain ${CLI OUTDIR} o.xml - Check Syslog Contains DebugFile: None + Check Syslog Contains Regexp DebugFile: None|No debug file Invalid Debugfile Create Directory ${CLI OUTDIR}${/}debug.txt From 98314fcaf58d4f375b6a3f8467e9a2d7064bcee9 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 11 Dec 2013 10:00:13 +0000 Subject: [PATCH 086/214] [python3] setup: Updated pkg info. --HG-- extra : transplant_source : %19/%3B%7E%D5R%FCG%8D%02%DF%9E%8B-%F4%E5s%14E%AB --- setup.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index d91d04de0be..1a5e5edbdb8 100755 --- a/setup.py +++ b/setup.py @@ -26,6 +26,7 @@ License :: OSI Approved :: Apache Software License Operating System :: OS Independent Programming Language :: Python +Programming Language :: Python :: 3 Topic :: Software Development :: Testing """.strip().splitlines() PACKAGES = ['robot', 'robot.api', 'robot.conf', @@ -54,12 +55,16 @@ version = get_version(sep=''), author = 'Robot Framework Developers', author_email = 'robotframework@gmail.com', - url = 'http://robotframework.org', - download_url = 'https://pypi.python.org/pypi/robotframework', + maintainer = 'Stefan Zimmermann', + maintainer_email = 'zimmermann.code@gmail.com', + url = 'https://bitbucket.org/userzimmermann' + '/robotframework-python3', + download_url = 'https://pypi.python.org/pypi/robotframework-python3', license = 'Apache License 2.0', - description = 'A generic test automation framework', + description = 'Python 3 compatible generic test automation framework', long_description = DESCRIPTION, - keywords = 'robotframework testing testautomation atdd bdd', + keywords = 'robotframework testing testautomation atdd bdd' + ' python3', platforms = 'any', classifiers = CLASSIFIERS, package_dir = {'': 'src'}, From 0c5a8655a70dbc771bfe675929608c3b6f74508b Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 11 Dec 2013 10:00:38 +0000 Subject: [PATCH 087/214] [python3] setup: Always use setuptools. --HG-- extra : transplant_source : %15-%E2K%DC%E4%1E%B6%88%EA%D64/e%B0CTp%7C%AC --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1a5e5edbdb8..515d96770f4 100755 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ import sys import os from os.path import join, dirname -from distutils.core import setup +from setuptools import setup if 'develop' in sys.argv: import setuptools # support setuptools development mode From 79fe94a0f69066a06064dfa568f8d356d7cfabf9 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 11 Dec 2013 10:16:59 +0000 Subject: [PATCH 088/214] [python3] README: Updated fork description. --HG-- extra : transplant_source : cu%C5%7C%DD%ECJ%8Ap%B2%D9%D2%22T%F3%E0%3E%E2%F8%11 --- README.txt | 74 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/README.txt b/README.txt index 9f19b933090..11e03205d64 100644 --- a/README.txt +++ b/README.txt @@ -1,28 +1,52 @@ -This is an unofficial Robot Framework Python 3.x compatibility fork. -It also remains compatible with all officially supported -Python 2.x platforms and versions, starting with 2.5. - -It uses the ``2to3`` tool in ``setup.py`` and ``atest/run_atests.py``. -The latter copies ``src/robot/`` and ``atest/`` to ``atest/python3/`` -before running the ``2to3`` script on them -and also converts some contents -of the Test Suite and Resource ``.txt`` files. - -``2to3`` can't handle everything... -Some fixers are disabled and there are also manual code changes. -The latter are mostly commented, with ``Python 3`` in the text, -or contain ``if sys.version_info[0] == 3``. -Manually changes in the acceptance Test Suites and Resources -mostly use ``Run on python 2.x`` and ``3.x`` Keywords for switching. - -You can also look at this URL for a complete diff: - -https://bitbucket.org/userzimmermann/robotframework-python3/compare/default..853a2e8#diff - -Most of the acceptance tests are already passing with Python 3. -Only ``36/3249`` critical tests are currently failing on my machine, -but this is mostly related to the tests themselves, -which need some further workarounds, switches and conversions. +Robot Framework with Python 3 compatibility +=========================================== + +- Forked from https://robotframework.googlecode.com +- Still compatible with all officially supported + Python 2.x platforms and versions, starting with 2.5 +- Not tested with Python 3 < 3.3 +- Invokes ``2to3`` in ``setup.py`` and ``atest/run_atests.py`` + in addition to manual code changes +- Goal is to make code completely 2/3 compatible without the need for 2to3, + at the cost of dropping Python 2.5 support + +Please report any issues to: + +https://bitbucket.org/userzimmermann/robotframework-python3/issues + +You can look at this URL for a complete code diff: + +https://bitbucket.org/userzimmermann/robotframework-python3/compare/master..robot#diff + +Differences in Python 3 +----------------------- + +Python 3 makes a clear distinction between ``str`` for textual data +and ``bytes`` for binary data. +This affects the Standard Test Libraries and their Keywords: + +- ``str`` arguments don't work where ``bytes`` are expected, + like writing to binary file streams or comparing with other ``bytes``. +- ``bytes`` don't work where ``str`` is expected, + like writing to text mode streams or comparing with another ``str``. +- Reading from binary streams always returns ``bytes``. +- Reading from text streams always returns ``str``. + +You can use the following keywords to explicitly create ``bytes``: + +- ``BuiltIn.Convert To Bytes`` +- ``String.Encode String To Bytes`` + +I extended ``Process.Start Process`` with a ``binary_mode`` argument. +By default the process streams are opened in text mode. +You can change this with setting ``binary_mode=True``. + +``Collections.Get Dictionary Keys`` normally sorts the keys. +I disabled key sorting in Python 3, +because most builtin types are not comparable to each other. +This further affects ``Get Dictionary Values`` and ``Get Dictionary Items``. +I still need to find a better solution... Maybe imitate Python 2 sorting? +Any suggestions? :) -- Stefan Zimmermann From 08077709c25e47644b8abe618a093a265f80e7f2 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 11 Dec 2013 10:59:12 +0000 Subject: [PATCH 089/214] [python3] robot_postinstall: Some compatibility changes. --HG-- extra : transplant_source : %B3NZXsP%96%DC%5EY%C1SM%3B%19%0F%25%7BV%FC --- robot_postinstall.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/robot_postinstall.py b/robot_postinstall.py index f8120dbf4f8..40a06b75231 100644 --- a/robot_postinstall.py +++ b/robot_postinstall.py @@ -32,14 +32,14 @@ def windows_install(): try: _create_script('jybot.bat', 'jython') _create_script('ipybot.bat', 'ipy') - except Exception, err: - print 'Running post-install script failed: %s' % err - print 'Robot Framework start-up scripts may not work correctly.' + except: + print('Running post-install script failed: %s' % sys.exc_info()[1]) + print('Robot Framework start-up scripts may not work correctly.') return # Avoid "close failed in file object destructor" error when UAC disabled # http://code.google.com/p/robotframework/issues/detail?id=1331 if sys.stdout.fileno() != -2: - print SUCCESS + print(SUCCESS) def _create_script(name, interpreter): From 939c453c0dc24a9c365c1182152e93a907923de8 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 16 Dec 2013 12:52:18 +0000 Subject: [PATCH 090/214] [python3] atest: ArgumentsPython: Modify method __doc__ containing sys.maxint --HG-- extra : transplant_source : %BA%E8%DD%1F%00%DA%D1rB/X%14%CF%0E%B2%EC%1F%0D%DCU --- atest/testresources/testlibs/ArgumentsPython.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/atest/testresources/testlibs/ArgumentsPython.py b/atest/testresources/testlibs/ArgumentsPython.py index 289b5b3bc72..e467627acee 100644 --- a/atest/testresources/testlibs/ArgumentsPython.py +++ b/atest/testresources/testlibs/ArgumentsPython.py @@ -1,3 +1,5 @@ +import sys + class ArgumentsPython: # method docs are used in unit tests as expected min and max args @@ -25,12 +27,21 @@ def a_1_3(self, arg1, arg2='default', arg3='default'): def a_0_n(self, *args): """(0,sys.maxint)""" return ' '.join(['a_0_n:', ' '.join(args)]) + + if sys.version_info[0] == 3: + a_0_n.__doc__ = """(0,sys.maxsize)""" def a_1_n(self, arg, *args): """(1,sys.maxint)""" return ' '.join(['a_1_n:', arg, ' '.join(args)]) + if sys.version_info[0] == 3: + a_1_n.__doc__ = """(1,sys.maxsize)""" + def a_1_2_n(self, arg1, arg2='default', *args): """(1,sys.maxint)""" return ' '.join(['a_1_2_n:', arg1, arg2, ' '.join(args)]) - \ No newline at end of file + + if sys.version_info[0] == 3: + a_1_2_n.__doc__ = """(1,sys.maxsize)""" + From 06afb581b158197db50468c0741ebdc6a4618e27 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 16 Dec 2013 12:53:49 +0000 Subject: [PATCH 091/214] [python3] model.Message: Only def __str__ in Python 3. --HG-- extra : transplant_source : %0E%22%C9P%0D%EEB%CCH%0F%F8%DB%88%D76%D4V%B2%10%A6 --- src/robot/model/message.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/robot/model/message.py b/src/robot/model/message.py index 09a8d5e40f2..f357c2974f4 100644 --- a/src/robot/model/message.py +++ b/src/robot/model/message.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys + from robot.utils import html_escape from .itemlist import ItemList @@ -51,8 +53,9 @@ def visit(self, visitor): def __unicode__(self): return self.message - def __str__(self): - return self.message + if sys.version_info[0] == 3: + def __str__(self): + return self.message class Messages(ItemList): From 3de7bfa73241bbfa19388331dc1c06e97bc49cbb Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 16 Dec 2013 12:59:54 +0000 Subject: [PATCH 092/214] [python3] model.stats.TagStat: Fixed __lt__. --HG-- extra : transplant_source : %BB%F2/%B0%1E%08%DA%9C%B8%03%28%E7jWaB%B4F%AC%8A --- src/robot/model/stats.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/robot/model/stats.py b/src/robot/model/stats.py index 636d6d9c02b..d58f3de4e16 100644 --- a/src/robot/model/stats.py +++ b/src/robot/model/stats.py @@ -175,9 +175,9 @@ def __cmp__(self, other): or Stat.__cmp__(self, other) def __lt__(self, other): - key = (other.critical, other.non_critical, other.combined, + key = (other.critical, other.non_critical, bool(other.combined), self._norm_name) - other_key = (self.critical, self.non_critical, self.combined, + other_key = (self.critical, self.non_critical, bool(self.combined), other._norm_name) return key < other_key From a29dbf65e791fb0faa3c656769c7cbc371dc0508 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 16 Dec 2013 14:06:23 +0000 Subject: [PATCH 093/214] [python3] __code__ to func_code for backwards compatibility --HG-- extra : transplant_source : %2C%E4%90%96%FA%96%3B%FFfsN%A5f%9DA%E8%60%90%E5_ --- src/robot/running/runkwregister.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/robot/running/runkwregister.py b/src/robot/running/runkwregister.py index f7d8b661fe8..9c0b70035d2 100644 --- a/src/robot/running/runkwregister.py +++ b/src/robot/running/runkwregister.py @@ -44,7 +44,7 @@ def _get_args_from_method(self, method): if inspect.ismethod(method): return method.im_func.func_code.co_argcount - 1 elif inspect.isfunction(method): - code = method.__code__ + code = method.func_code argcount = code.co_argcount # ...but you can look at the args: #TODO: Better solution? From 5c2261b6cd923a6b0a88869ad271330ce2c787e3 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 16 Dec 2013 14:08:17 +0000 Subject: [PATCH 094/214] [python3] Derive IllegalArgumentException dummy from Exception. --HG-- extra : transplant_source : DznK%3A%D3E%A5J%C3%8F%91%9B%83%CA%9A%BFq%C6%24 --- src/robot/running/signalhandler.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/robot/running/signalhandler.py b/src/robot/running/signalhandler.py index c16cd1af846..8acc4135dbc 100644 --- a/src/robot/running/signalhandler.py +++ b/src/robot/running/signalhandler.py @@ -21,7 +21,11 @@ if sys.platform.startswith('java'): from java.lang import IllegalArgumentException else: - IllegalArgumentException = None + ## IllegalArgumentException = None + # `None` doesn't work in Python 3 if used in `except` statement + # (in _register_signal_handler) + class IllegalArgumentException(Exception): + pass from robot.errors import ExecutionFailed from robot.output import LOGGER From be80ccac3c4ce34fdd7f1cb4b83627ab9b558f0f Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 16 Dec 2013 14:09:58 +0000 Subject: [PATCH 095/214] [python3] run_utests: 2to3 --HG-- extra : transplant_source : isD%CEo%93%1F%09%EFs%2A0%DB%E2%1Dgr%18%B6%26 --- utest/run_utests.py | 68 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/utest/run_utests.py b/utest/run_utests.py index 292dec3aebe..8ced4c225a6 100755 --- a/utest/run_utests.py +++ b/utest/run_utests.py @@ -17,9 +17,64 @@ import sys import re import getopt +import shutil +import subprocess +from os.path import join, abspath, dirname -base = os.path.abspath(os.path.normpath(os.path.split(sys.argv[0])[0])) +# Check for new working dir after 2to3: +if not 'UTESTDIR' in globals(): + # ==> still the original script before 2to3. + UTESTDIR = dirname(abspath(__file__)) +ROBOTDIR = join(UTESTDIR, '..', 'src', 'robot') +ATESTDIR = join(UTESTDIR, '..', 'atest') + +# If run with Python 3: +# - Copy src/robot/ and atest/ to atest/python3/ +# - Run 2to3 +# - Exec this file's copy in-place for actual testing + +# Is this script already the Python 3 copy? +if not 'do2to3' in globals(): + # ==> still the original. + do2to3 = True +if sys.version_info[0] == 3 and do2to3: + PY3DIR = join(UTESTDIR, 'python3') + PY3UTESTDIR = join(PY3DIR, 'utest') + PY3ATESTDIR = join(PY3DIR, 'atest') + + shutil.rmtree((PY3DIR), ignore_errors=True) + os.makedirs(join(PY3DIR, 'src')) + shutil.copytree(ROBOTDIR, join(PY3DIR, 'src', 'robot'), symlinks=True) + shutil.copytree( + UTESTDIR, PY3UTESTDIR, symlinks=True, + ignore=lambda src, names: names if src == PY3DIR else [] + ) + shutil.copytree( # ATESTDIR, PY3ATESTDIR, symlinks=True) + ATESTDIR, PY3ATESTDIR, symlinks=True, + ignore=lambda src, names: names if src == join(ATESTDIR, 'python3') else [] + ) + status = subprocess.call( + ['2to3', '--no-diffs', '-n', '-w', + '-x', 'dict', + '-x', 'filter', + PY3DIR + ]) + if status: + sys.exit(status) + + print("Hey Ho!") + do2to3 = False + UTESTDIR = PY3UTESTDIR + + # Exec this file's Python 3 copy: + TESTRUNNER = join(UTESTDIR, 'run_utests.py') + exec(open(TESTRUNNER).read()) + sys.exit(0) + + +base = UTESTDIR +## base = os.path.abspath(os.path.normpath(os.path.split(sys.argv[0])[0])) for path in ["../src", "../src/robot/libraries", "../src/robot", "../atest/testresources/testlibs" ]: path = os.path.join(base, path.replace('/', os.sep)) @@ -60,9 +115,9 @@ def parse_args(argv): options, args = getopt.getopt(argv, 'hH?vqd', ['help','verbose','quiet','doc']) if len(args) != 0: - raise getopt.error, 'no arguments accepted, got %s' % (args) - except getopt.error, err: - usage_exit(err) + raise getopt.error('no arguments accepted, got %s' % (args)) + except getopt.error: + usage_exit(sys.exc_info()[1]) for opt, value in options: if opt in ('-h','-H','-?','--help'): usage_exit() @@ -77,15 +132,16 @@ def parse_args(argv): def usage_exit(msg=None): - print __doc__ + print(__doc__) if msg is None: rc = 251 else: - print '\nError:', msg + print('\nError: %s' % msg) rc = 252 sys.exit(rc) +print(__name__) if __name__ == '__main__': docs, vrbst = parse_args(sys.argv[1:]) tests = get_tests() From cb5b80a7525e70bb3f99ed06ac86299cd5bd4940 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 16 Dec 2013 14:10:58 +0000 Subject: [PATCH 096/214] [python3] utest: Fixed ERRORs in Python 3. --HG-- extra : transplant_source : %9Ed%19%C9q%80%83%CA%15x%D5%19%F0%B4%13%16kz%B5%CE --- utest/api/test_exposed_api.py | 4 +++- utest/parsing/test_htmlreader.py | 1 - utest/parsing/test_tsvreader.py | 17 ++++++++++------- utest/reporting/test_jsbuildingcontext.py | 2 +- utest/result/test_resultserializer.py | 10 +++++++--- utest/running/test_handlers.py | 2 +- utest/utils/test_etreesource.py | 3 ++- utest/utils/test_importer_util.py | 2 ++ utest/utils/test_islike.py | 10 +++++++--- utest/utils/test_normalizing.py | 5 ++++- utest/utils/test_text.py | 10 +++++----- utest/utils/test_utf8reader.py | 9 ++++++--- 12 files changed, 48 insertions(+), 27 deletions(-) diff --git a/utest/api/test_exposed_api.py b/utest/api/test_exposed_api.py index 38ea6cba665..6ea08a5e009 100644 --- a/utest/api/test_exposed_api.py +++ b/utest/api/test_exposed_api.py @@ -33,7 +33,9 @@ def test_result_writer(self): class TestTestSuiteBuilder(unittest.TestCase): misc = join(abspath(__file__), '..', '..', '..', 'atest', 'testdata', 'misc') - sources = [join(misc, n) for n in 'pass_and_fail.txt', 'normal.txt'] + def sources(misc): + return [join(misc, n) for n in 'pass_and_fail.txt', 'normal.txt'] + sources = sources(misc) def test_create_with_datasources_as_list(self): suite = api.TestSuiteBuilder().build(*self.sources) diff --git a/utest/parsing/test_htmlreader.py b/utest/parsing/test_htmlreader.py index 45b6e316ad3..9d99c0b52e4 100644 --- a/utest/parsing/test_htmlreader.py +++ b/utest/parsing/test_htmlreader.py @@ -1,6 +1,5 @@ import sys import unittest -from types import UnicodeType from robot.parsing.htmlreader import HtmlReader from robot.utils.asserts import * diff --git a/utest/parsing/test_tsvreader.py b/utest/parsing/test_tsvreader.py index fdccc4b78c2..8fc5b9a45f5 100644 --- a/utest/parsing/test_tsvreader.py +++ b/utest/parsing/test_tsvreader.py @@ -1,5 +1,8 @@ import unittest -from StringIO import StringIO +try: + from io import BytesIO +except ImportError: # Python < 3 + from StringIO import StringIO as BytesIO from robot.parsing.tsvreader import TsvReader from robot.parsing.model import TestCaseFile @@ -19,7 +22,7 @@ def tearDown(self): robot.parsing.populators.PROCESS_CURDIR = self._orig_curdir def test_start_table(self): - tsv = StringIO('''*SettING*\t* Value *\t*V* + tsv = BytesIO('''*SettING*\t* Value *\t*V* ***Variable *Not*Table* @@ -27,13 +30,13 @@ def test_start_table(self): Keyword*\tNot a table because doesn't start with '*' *******************T*e*s*t*********C*a*s*e************\t***********\t******\t* -''') +'''.encode()) TsvReader().read(tsv, FromFilePopulator(self.tcf)) assert_equals(self.tcf.setting_table.name, 'SettING') assert_equals(self.tcf.setting_table.header, ['SettING','Value','V']) def test_rows(self): - tsv = StringIO('''Ignored text before tables... + tsv = BytesIO('''Ignored text before tables... Mote\tignored\text *Setting*\t*Value*\t*Value* Document\tWhatever\t\t\\\t @@ -42,7 +45,7 @@ def test_rows(self): *Variable*\tWhatever \\ \\ 2 escaped spaces before and after \\ \\\t\\ \\ value \\ \\ -''') +'''.encode()) TsvReader().read(tsv, FromFilePopulator(self.tcf)) assert_equals(self.tcf.setting_table.doc.value, 'Whatever ') assert_equals(self.tcf.setting_table.default_tags.value, ['t1','t2','t3']) @@ -51,7 +54,7 @@ def test_rows(self): def test_quotes(self): - tsv = StringIO('''*Variable*\t*Value* + tsv = BytesIO('''*Variable*\t*Value* ${v}\tHello ${v}\t"Hello" ${v}\t"""Hello""" @@ -60,7 +63,7 @@ def test_quotes(self): ${v}\t"""Hel "" """" lo""""""" ${v}\t"Hello ${v}\tHello" -''') +'''.encode()) TsvReader().read(tsv, FromFilePopulator(self.tcf)) actual = [variable for variable in self.tcf.variable_table.variables] expected = ['Hello','Hello','"Hello"','""Hello""','Hel"lo', diff --git a/utest/reporting/test_jsbuildingcontext.py b/utest/reporting/test_jsbuildingcontext.py index 862ab932436..0f3fa8df9d8 100644 --- a/utest/reporting/test_jsbuildingcontext.py +++ b/utest/reporting/test_jsbuildingcontext.py @@ -72,7 +72,7 @@ def test_info_is_smallest_when_no_debug_or_trace(self): assert_equals('INFO', self._context.min_level) def _messages(self, levels): - levels = levels[:] + levels = list(levels) random.shuffle(levels) for level in levels: self._context.message_level(level) diff --git a/utest/result/test_resultserializer.py b/utest/result/test_resultserializer.py index 7eeec1662c7..8585ed522e1 100644 --- a/utest/result/test_resultserializer.py +++ b/utest/result/test_resultserializer.py @@ -1,6 +1,10 @@ from __future__ import with_statement import unittest -from StringIO import StringIO +try: + from io import StringIO, BytesIO +except ImportError: # Python < 3 + from StringIO import StringIO + BytesIO = StringIO from robot.result import ExecutionResult from robot.reporting.outputwriter import OutputWriter @@ -40,13 +44,13 @@ def test_single_result_serialization(self): def _xml_lines(self, text): with ETSource(text) as source: tree = ET.parse(source) - output = StringIO() + output = BytesIO() tree.write(output) return output.getvalue().splitlines() def _assert_xml_content(self, actual, expected): assert_equals(len(actual), len(expected)) - for index, (act, exp) in enumerate(zip(actual, expected)[2:]): + for index, (act, exp) in enumerate(list(zip(actual, expected))[2:]): assert_equals(act, exp.strip(), 'Different values on line %d' % index) def test_combining_results(self): diff --git a/utest/running/test_handlers.py b/utest/running/test_handlers.py index 0a76adea2d6..9927215a318 100644 --- a/utest/running/test_handlers.py +++ b/utest/running/test_handlers.py @@ -85,7 +85,7 @@ def test_non_empty_doc(self): def test_non_ascii_doc(self): self._assert_doc(u'P\xe4iv\xe4\xe4') - if sys.platform != 'cli': + if sys.platform != 'cli' and sys.version_info[0] < 3: def test_with_utf8_doc(self): doc = u'P\xe4iv\xe4\xe4' self._assert_doc(doc.encode('UTF-8'), doc) diff --git a/utest/utils/test_etreesource.py b/utest/utils/test_etreesource.py index af902fbe5bd..905008c8187 100644 --- a/utest/utils/test_etreesource.py +++ b/utest/utils/test_etreesource.py @@ -8,6 +8,7 @@ from robot.errors import DataError IRONPYTHON = sys.platform == 'cli' +PYTHON3 = sys.version_info[0] == 3 PATH = os.path.join(os.path.dirname(__file__), 'test_etreesource.py') @@ -44,7 +45,7 @@ def _test_string(self, xml): source = ETSource(xml) with source as src: content = src.read() - if not IRONPYTHON: + if not (IRONPYTHON or PYTHON3): content = content.decode('UTF-8') assert_equals(content, xml) self._verify_string_representation(source, '') diff --git a/utest/utils/test_importer_util.py b/utest/utils/test_importer_util.py index 72b38058b86..3d4c53cd620 100644 --- a/utest/utils/test_importer_util.py +++ b/utest/utils/test_importer_util.py @@ -1,4 +1,5 @@ from __future__ import with_statement +import time import unittest import tempfile import inspect @@ -152,6 +153,7 @@ def _import(self, path, name=None, remove=None): importer = Importer(name, self.logger) sys_path_before = sys.path[:] try: + time.sleep(2) return importer.import_class_or_module_by_path(path) finally: assert_equals(sys.path, sys_path_before) diff --git a/utest/utils/test_islike.py b/utest/utils/test_islike.py index 61ed0795123..404f2023a79 100644 --- a/utest/utils/test_islike.py +++ b/utest/utils/test_islike.py @@ -11,9 +11,13 @@ except ImportError: pass from array import array -from UserDict import UserDict -from UserList import UserList -from UserString import UserString, MutableString +try: + from UserDict import UserDict + from UserList import UserList + from UserString import UserString, MutableString +except ImportError: # Python 3 + from collections import UserDict, UserList, UserString + MutableString = UserString from robot.utils import is_dict_like, is_list_like, is_str_like from robot.utils.asserts import assert_equals diff --git a/utest/utils/test_normalizing.py b/utest/utils/test_normalizing.py index d99cad77d8f..833ab9d2e87 100644 --- a/utest/utils/test_normalizing.py +++ b/utest/utils/test_normalizing.py @@ -1,5 +1,8 @@ import unittest -from UserDict import UserDict +try: + from UserDict import UserDict +except ImportError: # Python 3 + from collections import UserDict from robot.utils import normalize, NormalizedDict from robot.utils.asserts import (assert_equals, assert_true, assert_false, diff --git a/utest/utils/test_text.py b/utest/utils/test_text.py index 74ef2249b7c..d85e82f44a3 100644 --- a/utest/utils/test_text.py +++ b/utest/utils/test_text.py @@ -30,7 +30,7 @@ class TestCutting(unittest.TestCase): def setUp(self): self.lines = [ 'my error message %d' % i for i in range(_MAX_ERROR_LINES+1) ] self.result = cut_long_message('\n'.join(self.lines)).splitlines() - self.limit = _MAX_ERROR_LINES/2 + self.limit = _MAX_ERROR_LINES//2 def test_more_than_max_number_of_lines(self): assert_equal(len(self.result), _MAX_ERROR_LINES+1) @@ -63,8 +63,8 @@ def test_correct_number_of_lines(self): assert_equal(sum(_count_line_lengths(self.result)), _MAX_ERROR_LINES+1) def test_correct_lines(self): - excpected = self.lines[:_MAX_ERROR_LINES/2] + [_ERROR_CUT_EXPLN] \ - + self.lines[-_MAX_ERROR_LINES/2+1:] + excpected = self.lines[:_MAX_ERROR_LINES//2] + [_ERROR_CUT_EXPLN] \ + + self.lines[-_MAX_ERROR_LINES//2+1:] assert_equal(self.result, excpected) def test_every_line_longer_than_limit(self): @@ -81,7 +81,7 @@ class TestCutHappensInsideLine(unittest.TestCase): def test_long_line_cut_before_cut_message(self): lines = ['line %d' % i for i in range(_MAX_ERROR_LINES)] - index = _MAX_ERROR_LINES/2-1 + index = _MAX_ERROR_LINES//2-1 lines[index] = 'abcdefgh' * _MAX_ERROR_LINE_LENGTH result = cut_long_message('\n'.join(lines)).splitlines() self._assert_basics(result, lines) @@ -90,7 +90,7 @@ def test_long_line_cut_before_cut_message(self): def test_long_line_cut_after_cut_message(self): lines = ['line %d' % i for i in range(_MAX_ERROR_LINES)] - index = _MAX_ERROR_LINES/2 + index = _MAX_ERROR_LINES//2 lines[index] = 'abcdefgh' * _MAX_ERROR_LINE_LENGTH result = cut_long_message('\n'.join(lines)).splitlines() self._assert_basics(result, lines) diff --git a/utest/utils/test_utf8reader.py b/utest/utils/test_utf8reader.py index 505ea9b1278..5d8b2584662 100644 --- a/utest/utils/test_utf8reader.py +++ b/utest/utils/test_utf8reader.py @@ -1,6 +1,9 @@ from __future__ import with_statement from codecs import BOM_UTF8 -from StringIO import StringIO +try: + from io import BytesIO +except ImportError: # Python < 3 + from StringIO import StringIO as BytesIO import os import tempfile import unittest @@ -43,7 +46,7 @@ def test_must_open_in_binary_mode(self): assert_raises(ValueError, Utf8Reader, f) def test_stringio_is_ok(self): - f = StringIO(self.BOM + STRING.encode('UTF-8')) + f = BytesIO(self.BOM + STRING.encode('UTF-8')) with Utf8Reader(f) as reader: assert_equals(reader.read(), STRING) assert_equals(f.closed, False) @@ -59,7 +62,7 @@ def test_invalid_encoding(self): class TestUtf8ReaderWithoutBom(TestUtf8ReaderWithBom): - BOM = '' + BOM = ''.encode() if __name__ == '__main__': From f09cb74d54663aef5109679cb9556b92a11e17a8 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 16 Dec 2013 14:12:29 +0000 Subject: [PATCH 097/214] [python3] README: 2to3 in run_utests.py --HG-- extra : transplant_source : %EC%CE%ADCo%09%F3fp%0C%00%1E%EF%DD%DF%A0%3D%CE%99%97 --- README.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.txt b/README.txt index 11e03205d64..85486b31645 100644 --- a/README.txt +++ b/README.txt @@ -5,8 +5,8 @@ Robot Framework with Python 3 compatibility - Still compatible with all officially supported Python 2.x platforms and versions, starting with 2.5 - Not tested with Python 3 < 3.3 -- Invokes ``2to3`` in ``setup.py`` and ``atest/run_atests.py`` - in addition to manual code changes +- Invokes ``2to3`` in ``setup.py``, ``atest/run_atests.py`` + and ``utest/run_utests.py`` in addition to manual code changes - Goal is to make code completely 2/3 compatible without the need for 2to3, at the cost of dropping Python 2.5 support From 8881eaf6e8fae8f6e401cc2b8c258f59db00a6b3 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 16 Dec 2013 14:20:42 +0000 Subject: [PATCH 098/214] [python3] Removed some debug prints. --HG-- extra : transplant_source : %F96%ECg%AD%24%81%A8%07%BC%28%3D%CB%99%83%A6J%88%99%A3 --- utest/run_utests.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/utest/run_utests.py b/utest/run_utests.py index 8ced4c225a6..589f7282d3a 100755 --- a/utest/run_utests.py +++ b/utest/run_utests.py @@ -63,7 +63,6 @@ if status: sys.exit(status) - print("Hey Ho!") do2to3 = False UTESTDIR = PY3UTESTDIR @@ -141,7 +140,6 @@ def usage_exit(msg=None): sys.exit(rc) -print(__name__) if __name__ == '__main__': docs, vrbst = parse_args(sys.argv[1:]) tests = get_tests() From 69553534d44a56df2362ffe3badf0a07c438f76e Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 19 Dec 2013 20:57:40 +0000 Subject: [PATCH 099/214] [python3] robot.model: Some .__bytes__() and .__str__() for Python 3. --HG-- extra : transplant_source : %23%1E%3F%25%3C%E6%B1%E6n%B3%E9%C2%1FO1%FA%81%5E7%C2 --- src/robot/model/metadata.py | 4 ++++ src/robot/model/modelobject.py | 4 ++++ src/robot/model/tags.py | 8 ++++++++ 3 files changed, 16 insertions(+) diff --git a/src/robot/model/metadata.py b/src/robot/model/metadata.py index aaeb568f2e8..d2ce4cb820c 100644 --- a/src/robot/model/metadata.py +++ b/src/robot/model/metadata.py @@ -29,3 +29,7 @@ def __str__(self): if sys.version_info[0] == 3: return self.__unicode__() return unicode(self).encode('ASCII', 'replace') + + if sys.version_info[0] == 3: + def __bytes__(self): + return str(self).encode('ASCII', 'replace') diff --git a/src/robot/model/modelobject.py b/src/robot/model/modelobject.py index 54ef060a202..a2d1412fdc0 100644 --- a/src/robot/model/modelobject.py +++ b/src/robot/model/modelobject.py @@ -29,5 +29,9 @@ def __str__(self): return self.__unicode__() return unicode(self).encode('ASCII', 'replace') + if sys.version_info[0] == 3: + def __bytes__(self): + return str(self).encode('ASCII', 'replace') + def __repr__(self): return repr(str(self)) diff --git a/src/robot/model/tags.py b/src/robot/model/tags.py index bd3acfbd6a9..ef9a99eb8cc 100644 --- a/src/robot/model/tags.py +++ b/src/robot/model/tags.py @@ -67,6 +67,10 @@ def __str__(self): return self.__unicode__() return unicode(self).encode('UTF-8') + if sys.version_info[0] == 3: + def __bytes__(self): + return str(self).encode('UTF-8') + def __getitem__(self, index): item = self._tags[index] return item if not isinstance(index, slice) else Tags(item) @@ -117,6 +121,10 @@ def match(self, tags): def __unicode__(self): return self._matcher.pattern + if sys.version_info[0] == 3: + def __str__(self): + return self._matcher.pattern + class _AndTagPattern(object): From 5d1623035c8666338d42d3b4024c566983a16f07 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 19 Dec 2013 21:02:48 +0000 Subject: [PATCH 100/214] [python3] utest/model: test_bytes() instead of test_str() in Python 3. --HG-- extra : transplant_source : %E1%F3%AE%F6Lf%8B%07%A3H5Ui%AE%128%9AA%B1%DC --- utest/model/test_keyword.py | 16 ++++++++++++---- utest/model/test_message.py | 16 ++++++++++++---- utest/model/test_metadata.py | 16 ++++++++++++---- utest/model/test_tags.py | 17 +++++++++++++---- utest/model/test_testcase.py | 16 ++++++++++++---- utest/model/test_testsuite.py | 16 ++++++++++++---- 6 files changed, 73 insertions(+), 24 deletions(-) diff --git a/utest/model/test_keyword.py b/utest/model/test_keyword.py index 3a424d8b914..dcab49714a4 100644 --- a/utest/model/test_keyword.py +++ b/utest/model/test_keyword.py @@ -1,3 +1,4 @@ +import sys import unittest from robot.utils.asserts import assert_equal, assert_true, assert_raises @@ -40,10 +41,17 @@ def test_unicode(self): assert_equal(unicode(self.ascii), 'Kekkonen') assert_equal(unicode(self.non_ascii), u'hyv\xe4 nimi') - def test_str(self): - assert_equal(str(self.empty), '') - assert_equal(str(self.ascii), 'Kekkonen') - assert_equal(str(self.non_ascii), 'hyv? nimi') + if sys.version_info[0] < 3: + def test_str(self): + assert_equal(str(self.empty), '') + assert_equal(str(self.ascii), 'Kekkonen') + assert_equal(str(self.non_ascii), 'hyv? nimi') + + else: + def test_bytes(self): + assert_equal(bytes(self.empty), ''.encode()) + assert_equal(bytes(self.ascii), 'Kekkonen'.encode()) + assert_equal(bytes(self.non_ascii), 'hyv? nimi'.encode()) class TestKeywords(unittest.TestCase): diff --git a/utest/model/test_message.py b/utest/model/test_message.py index 79e84bc4648..ae24f83060c 100644 --- a/utest/model/test_message.py +++ b/utest/model/test_message.py @@ -1,3 +1,4 @@ +import sys import unittest from robot.model import Message @@ -32,10 +33,17 @@ def test_unicode(self): assert_equal(unicode(self.ascii), 'Kekkonen') assert_equal(unicode(self.non_ascii), u'hyv\xe4 nimi') - def test_str(self): - assert_equal(str(self.empty), '') - assert_equal(str(self.ascii), 'Kekkonen') - assert_equal(str(self.non_ascii), 'hyv? nimi') + if sys.version_info[0] < 3: + def test_str(self): + assert_equal(str(self.empty), '') + assert_equal(str(self.ascii), 'Kekkonen') + assert_equal(str(self.non_ascii), 'hyv? nimi') + + else: + def test_bytes(self): + assert_equal(bytes(self.empty), ''.encode()) + assert_equal(bytes(self.ascii), 'Kekkonen'.encode()) + assert_equal(bytes(self.non_ascii), 'hyv? nimi'.encode()) def test_slots(self): assert_raises(AttributeError, setattr, Message(), 'attr', 'value') diff --git a/utest/model/test_metadata.py b/utest/model/test_metadata.py index 68ad7f21252..20f1668123c 100644 --- a/utest/model/test_metadata.py +++ b/utest/model/test_metadata.py @@ -1,3 +1,4 @@ +import sys import unittest from robot.utils.asserts import assert_equal @@ -15,10 +16,17 @@ def test_unicode(self): d = {'a': 1, 'B': 'two', u'\xe4': u'nelj\xe4'} assert_equal(unicode(Metadata(d)), u'{a: 1, B: two, \xe4: nelj\xe4}') - def test_str(self): - assert_equal(str(Metadata()), '{}') - d = {'a': 1, 'B': 'two', u'\xe4': u'nelj\xe4'} - assert_equal(str(Metadata(d)), '{a: 1, B: two, ?: nelj?}') + if sys.version_info[0] < 3: + def test_str(self): + assert_equal(str(Metadata()), '{}') + d = {'a': 1, 'B': 'two', u'\xe4': u'nelj\xe4'} + assert_equal(str(Metadata(d)), '{a: 1, B: two, ?: nelj?}') + + else: + def test_bytes(self): + assert_equal(bytes(Metadata()), '{}'.encode()) + d = {'a': 1, 'B': 'two', u'\xe4': u'nelj\xe4'} + assert_equal(bytes(Metadata(d)), '{a: 1, B: two, ?: nelj?}'.encode()) if __name__ == '__main__': diff --git a/utest/model/test_tags.py b/utest/model/test_tags.py index 03f93df5fbe..d8d415fe573 100644 --- a/utest/model/test_tags.py +++ b/utest/model/test_tags.py @@ -1,3 +1,4 @@ +import sys import unittest from robot.utils.asserts import assert_equal, assert_true, assert_false @@ -89,10 +90,18 @@ def test_unicode(self): assert_equal(unicode(Tags(['y', "X'X", 'Y'])), "[X'X, y]") assert_equal(unicode(Tags([u'\xe4', 'a'])), u'[a, \xe4]') - def test_str(self): - assert_equal(str(Tags()), '[]') - assert_equal(str(Tags(['y', "X'X"])), "[X'X, y]") - assert_equal(str(Tags([u'\xe4', 'a'])), '[a, \xc3\xa4]') + if sys.version_info[0] < 3: + def test_str(self): + assert_equal(str(Tags()), '[]') + assert_equal(str(Tags(['y', "X'X"])), "[X'X, y]") + assert_equal(str(Tags([u'\xe4', 'a'])), '[a, \xc3\xa4]') + + else: + def test_bytes(self): + assert_equal(bytes(Tags()), '[]'.encode()) + assert_equal(bytes(Tags(['y', "X'X"])), "[X'X, y]".encode()) + assert_equal(bytes(Tags([u'\xe4', 'a'])), + '[a, \xc3\xa4]'.encode('latin')) def test_repr(self): for tags in ([], ['y', "X'X"], [u'\xe4', 'a']): diff --git a/utest/model/test_testcase.py b/utest/model/test_testcase.py index 0a72da4a273..dda17f3b66f 100644 --- a/utest/model/test_testcase.py +++ b/utest/model/test_testcase.py @@ -1,3 +1,4 @@ +import sys import unittest from robot.utils.asserts import assert_equal, assert_raises @@ -51,10 +52,17 @@ def test_unicode(self): assert_equal(unicode(self.ascii), 'Kekkonen') assert_equal(unicode(self.non_ascii), u'hyv\xe4 nimi') - def test_str(self): - assert_equal(str(self.empty), '') - assert_equal(str(self.ascii), 'Kekkonen') - assert_equal(str(self.non_ascii), 'hyv? nimi') + if sys.version_info[0] < 3: + def test_str(self): + assert_equal(str(self.empty), '') + assert_equal(str(self.ascii), 'Kekkonen') + assert_equal(str(self.non_ascii), 'hyv? nimi') + + else: + def test_bytes(self): + assert_equal(bytes(self.empty), ''.encode()) + assert_equal(bytes(self.ascii), 'Kekkonen'.encode()) + assert_equal(bytes(self.non_ascii), 'hyv? nimi'.encode()) if __name__ == '__main__': diff --git a/utest/model/test_testsuite.py b/utest/model/test_testsuite.py index c483af6b5e4..09cae1b3a57 100644 --- a/utest/model/test_testsuite.py +++ b/utest/model/test_testsuite.py @@ -1,3 +1,4 @@ +import sys import unittest from robot.utils.asserts import assert_equal, assert_true, assert_raises @@ -111,10 +112,17 @@ def test_unicode(self): assert_equal(unicode(self.ascii), 'Kekkonen') assert_equal(unicode(self.non_ascii), u'hyv\xe4 nimi') - def test_str(self): - assert_equal(str(self.empty), '') - assert_equal(str(self.ascii), 'Kekkonen') - assert_equal(str(self.non_ascii), 'hyv? nimi') + if sys.version_info[0] < 3: + def test_str(self): + assert_equal(str(self.empty), '') + assert_equal(str(self.ascii), 'Kekkonen') + assert_equal(str(self.non_ascii), 'hyv? nimi') + + else: + def test_bytes(self): + assert_equal(bytes(self.empty), ''.encode()) + assert_equal(bytes(self.ascii), 'Kekkonen'.encode()) + assert_equal(bytes(self.non_ascii), 'hyv? nimi'.encode()) if __name__ == '__main__': From 18da3bec3fd2286c8cee88996d464627c479b86a Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 19 Dec 2013 23:43:18 +0000 Subject: [PATCH 101/214] [python3] utils.ETSource: encoding. --HG-- extra : transplant_source : %FC%D72%81q%8F%5D%26%13%17%A9%87%16tTy%A4l%CA%02 --- src/robot/utils/etreewrapper.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/robot/utils/etreewrapper.py b/src/robot/utils/etreewrapper.py index 28a4e8bf841..0f2f46fd5ed 100644 --- a/src/robot/utils/etreewrapper.py +++ b/src/robot/utils/etreewrapper.py @@ -17,6 +17,8 @@ from StringIO import StringIO +PY3 = sys.version_info[0] == 3 + _IRONPYTHON = sys.platform == 'cli' _ERROR = 'No valid ElementTree XML parser module found' @@ -53,8 +55,9 @@ class ETSource(object): - def __init__(self, source): + def __init__(self, source, encoding='UTF-8'): self._source = source + self._encoding = encoding self._opened = None def __enter__(self): @@ -90,16 +93,14 @@ def _open_source_if_necessary(self): # especially on Windows: http://bugs.jython.org/issue1598 # The bug has now been fixed in ET and worked around in Jython 2.5.2. - if sys.version_info[0] == 3: - + if PY3: def _open_file(self, source): - return open(source, 'r') + return open(source, 'r', encoding=self._encoding) def _open_string_io(self, source): return StringIO(source) else: - def _open_file(self, source): return open(source, 'rb') From 7d687b2383ccd45fed0ea3f097f1b2b1edbfe662 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 19 Dec 2013 23:44:25 +0000 Subject: [PATCH 102/214] [python3] _MarkupWriter: Better compatibility workarounds. --HG-- extra : transplant_source : %CF%FF%FA%27%5D%AE%F8%D6%2B6%83%C8%C4%7E%F5U%3F%9E%8D%DC --- src/robot/utils/markupwriters.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/robot/utils/markupwriters.py b/src/robot/utils/markupwriters.py index ee8cc3abd40..2fc4e1de4ba 100644 --- a/src/robot/utils/markupwriters.py +++ b/src/robot/utils/markupwriters.py @@ -12,9 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys + from .markuputils import html_escape, xml_escape, attribute_escape +PY3 = sys.version_info[0] == 3 + + class _MarkupWriter(object): def __init__(self, output, line_separator='\n', encoding='UTF-8'): @@ -27,10 +32,14 @@ def __init__(self, output, line_separator='\n', encoding='UTF-8'): output file. If `None`, text will not be encoded. """ if isinstance(output, basestring): - output = open(output, 'w') + if PY3: + output = open(output, 'w', encoding=encoding) + else: + output = open(output, 'w') + self._encode_output = not hasattr(output, 'encoding') self.output = output - self._line_separator = line_separator self._encoding = encoding + self._line_separator = self._encode(line_separator) self._preamble() def _preamble(self): @@ -71,16 +80,12 @@ def close(self): self.output.close() def _write(self, text, newline=False): - encoded_text = self._encode(text) - try: - self.output.write(encoded_text) - except TypeError: # Python 3 - self.output.write(text) + self.output.write(self._encode(text)) if newline: self.output.write(self._line_separator) def _encode(self, text): - return text.encode(self._encoding) if self._encoding else text + return text.encode(self._encoding) if self._encode_output else text class HtmlWriter(_MarkupWriter): From 568d0e0953619f6306d01b1a1c837bbcb0afff8d Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 19 Dec 2013 23:47:46 +0000 Subject: [PATCH 103/214] [python3] utest: Some encoding related workarounds. --HG-- extra : transplant_source : %DC%9F%F7FD%D8nI%BE%5E%EC6%ED%FE%D6%A8%3FU%D9G --- utest/reporting/test_reporting.py | 2 ++ utest/utils/test_htmlwriter.py | 12 ++++++++++-- utest/utils/test_importer_util.py | 1 - utest/utils/test_misc.py | 8 ++++++-- utest/utils/test_xmlwriter.py | 26 +++++++++++++++----------- 5 files changed, 33 insertions(+), 16 deletions(-) diff --git a/utest/reporting/test_reporting.py b/utest/reporting/test_reporting.py index cce7bc67271..546b132630d 100644 --- a/utest/reporting/test_reporting.py +++ b/utest/reporting/test_reporting.py @@ -141,6 +141,8 @@ def __init__(self, **settings): class ClosableOutput(object): + encoding = None + def __init__(self, path): self._output = StringIO() self._path = path diff --git a/utest/utils/test_htmlwriter.py b/utest/utils/test_htmlwriter.py index a91bb3a2458..aacfb96c0aa 100644 --- a/utest/utils/test_htmlwriter.py +++ b/utest/utils/test_htmlwriter.py @@ -1,6 +1,14 @@ +import sys import os import unittest -from StringIO import StringIO + +PY3 = sys.version_info[0] == 3 + +if PY3: + from io import BytesIO, StringIO +else: + from StringIO import StringIO + BytesIO = StringIO from robot.utils import HtmlWriter from robot.utils.asserts import assert_equals @@ -115,7 +123,7 @@ def test_encoding(self): self._test_encoding('ISO-8859-1') def _test_encoding(self, encoding): - self.output = StringIO() + self.output = BytesIO() writer = HtmlWriter(self.output, encoding=encoding) writer.start(u'p', attrs={'name': u'hyv\xe4\xe4'}, newline=False) writer.content(u'y\xf6') diff --git a/utest/utils/test_importer_util.py b/utest/utils/test_importer_util.py index 3d4c53cd620..8ea3dd724c2 100644 --- a/utest/utils/test_importer_util.py +++ b/utest/utils/test_importer_util.py @@ -153,7 +153,6 @@ def _import(self, path, name=None, remove=None): importer = Importer(name, self.logger) sys_path_before = sys.path[:] try: - time.sleep(2) return importer.import_class_or_module_by_path(path) finally: assert_equals(sys.path, sys_path_before) diff --git a/utest/utils/test_misc.py b/utest/utils/test_misc.py index 847d7047d49..59aa32a0c2b 100644 --- a/utest/utils/test_misc.py +++ b/utest/utils/test_misc.py @@ -10,6 +10,8 @@ IPY = sys.platform == 'cli' +PY3 = sys.version_info[0] == 3 + class TestMiscUtils(unittest.TestCase): @@ -73,13 +75,15 @@ class Class: def test_non_ascii_doc_in_utf8(self): def func(): """Hyv\xc3\xa4 \xc3\xa4iti!""" - expected = u'Hyv\xe4 \xe4iti!' if not IPY else u'Hyv\xc3\xa4 \xc3\xa4iti!' + expected = (u'Hyv\xe4 \xe4iti!' if not (IPY or PY3) + else u'Hyv\xc3\xa4 \xc3\xa4iti!') assert_equals(getdoc(func), expected) def test_non_ascii_doc_not_in_utf8(self): def func(): """Hyv\xe4 \xe4iti!""" - expected = 'Hyv\\xe4 \\xe4iti!' if not IPY else u'Hyv\xe4 \xe4iti!' + expected = ('Hyv\\xe4 \\xe4iti!' if not (IPY or PY3) + else u'Hyv\xe4 \xe4iti!') assert_equals(getdoc(func), expected) def test_unicode_doc(self): diff --git a/utest/utils/test_xmlwriter.py b/utest/utils/test_xmlwriter.py index 38aa809d885..c0ab836571c 100644 --- a/utest/utils/test_xmlwriter.py +++ b/utest/utils/test_xmlwriter.py @@ -1,4 +1,5 @@ from __future__ import with_statement +import sys import os import unittest import tempfile @@ -6,6 +7,8 @@ from robot.utils import XmlWriter, ET, ETSource from robot.utils.asserts import * +PY3 = sys.version_info[0] == 3 + PATH = os.path.join(tempfile.gettempdir(), 'test_xmlwriter.xml') @@ -102,33 +105,34 @@ def test_ioerror_when_file_is_invalid(self): assert_raises(IOError, XmlWriter, os.path.dirname(__file__)) def test_custom_encoding(self): + encoding='ISO-8859-1' self.writer.close() - self.writer = XmlWriter(PATH, encoding='ISO-8859-1') + self.writer = XmlWriter(PATH, encoding=encoding) self.writer.element('test', u'hyv\xe4') - self._verify_content('encoding="ISO-8859-1"') - self._verify_node(None, 'test', u'hyv\xe4') + self._verify_content('encoding="ISO-8859-1"', encoding=encoding) + self._verify_node(None, 'test', u'hyv\xe4', encoding=encoding) - def _verify_node(self, node, name, text=None, attrs={}): + def _verify_node(self, node, name, text=None, attrs={}, encoding='UTF-8'): if node is None: - node = self._get_root() + node = self._get_root(encoding=encoding) assert_equals(node.tag, name) if text is not None: assert_equals(node.text, text) assert_equals(node.attrib, attrs) - def _verify_content(self, expected): - content = self._get_content() + def _verify_content(self, expected, encoding='UTF-8'): + content = self._get_content(encoding) assert_true(expected in content, 'Failed to find:\n%s\n\nfrom:\n%s' % (expected, content)) - def _get_root(self): + def _get_root(self, encoding='UTF-8'): self.writer.close() - with ETSource(PATH) as source: + with ETSource(PATH, encoding=encoding) as source: return ET.parse(source).getroot() - def _get_content(self): + def _get_content(self, encoding='UTF-8'): self.writer.close() - with open(PATH) as f: + with open(PATH, encoding=encoding) if PY3 else open(PATH) as f: return f.read() From 9987dee28fe41328f7abfc431f6cef0123d3fd6b Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 20 Dec 2013 09:30:56 +0000 Subject: [PATCH 104/214] [python3] utils.ETSource: Reverted optional encoding arg. Added ._open_bytes_io(). --HG-- extra : transplant_source : %24%7Bw%C8W%FD%94%3F%3Ep%BE%F4X%CB%14%23%CB%E7%97%84 --- src/robot/utils/etreewrapper.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/robot/utils/etreewrapper.py b/src/robot/utils/etreewrapper.py index 0f2f46fd5ed..6873d1dc7a0 100644 --- a/src/robot/utils/etreewrapper.py +++ b/src/robot/utils/etreewrapper.py @@ -14,11 +14,14 @@ import sys import os.path -from StringIO import StringIO - PY3 = sys.version_info[0] == 3 +if PY3: + from io import BytesIO +from StringIO import StringIO + + _IRONPYTHON = sys.platform == 'cli' _ERROR = 'No valid ElementTree XML parser module found' @@ -55,9 +58,8 @@ class ETSource(object): - def __init__(self, source, encoding='UTF-8'): + def __init__(self, source): self._source = source - self._encoding = encoding self._opened = None def __enter__(self): @@ -84,6 +86,8 @@ def _open_source_if_necessary(self): return self._open_file(self._source) if isinstance(self._source, basestring): return self._open_string_io(self._source) + if PY3 and isinstance(self._source, bytes): + return self._open_bytes_io(self._source) return None if not _IRONPYTHON: @@ -92,18 +96,17 @@ def _open_source_if_necessary(self): # it didn't close files it had opened. This caused problems with Jython # especially on Windows: http://bugs.jython.org/issue1598 # The bug has now been fixed in ET and worked around in Jython 2.5.2. + def _open_file(self, source): + return open(source, 'rb') if PY3: - def _open_file(self, source): - return open(source, 'r', encoding=self._encoding) - def _open_string_io(self, source): return StringIO(source) - else: - def _open_file(self, source): - return open(source, 'rb') + def _open_bytes_io(self, source): + return BytesIO(source) + else: def _open_string_io(self, source): return StringIO(source.encode('UTF-8')) From 0708f4b2a1a6e0f53c4ddb1cbca840777ecef3ad Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 20 Dec 2013 09:32:00 +0000 Subject: [PATCH 105/214] [python3] NormalizedDict.__eq__ --HG-- extra : transplant_source : %2AP%B7%26%A7%087h%7E%81%1C%C0%14%8Fu%3F%B6%01y%84 --- src/robot/utils/normalizing.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/robot/utils/normalizing.py b/src/robot/utils/normalizing.py index e966964cfbb..ae45350c380 100644 --- a/src/robot/utils/normalizing.py +++ b/src/robot/utils/normalizing.py @@ -174,3 +174,8 @@ def __cmp__(self, other): if not isinstance(other, NormalizedDict) and isinstance(other, mappings): other = NormalizedDict(other) return UserDict.__cmp__(self, other) + + def __eq__(self, other): + if not isinstance(other, NormalizedDict) and isinstance(other, mappings): + other = NormalizedDict(other).data + return self.data == other From f5b7d1f3599bf2fa23982359eca5de7fd83b2a5a Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 20 Dec 2013 09:33:17 +0000 Subject: [PATCH 106/214] [python3] utest: utils/test_etreesource: Some explicit byte strings. --HG-- extra : transplant_source : Kuo%3B%BAP%FB%23%E8%AD%9Bf%82F%98%E0%0F%B1%FC%DF --- utest/utils/test_etreesource.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utest/utils/test_etreesource.py b/utest/utils/test_etreesource.py index 905008c8187..b03611ece4a 100644 --- a/utest/utils/test_etreesource.py +++ b/utest/utils/test_etreesource.py @@ -20,7 +20,7 @@ def test_path_to_file(self): if IRONPYTHON: assert_equals(src, PATH) else: - assert_true(src.read().startswith('from __future__')) + assert_true(src.read().startswith('from __future__'.encode())) self._verify_string_representation(source, PATH) if IRONPYTHON: assert_true(source._opened is None) @@ -36,7 +36,7 @@ def test_opened_file_object(self): assert_true(source._opened is None) def test_byte_string(self): - self._test_string('\ncontent\n') + self._test_string('\ncontent\n'.encode()) def test_unicode_string(self): self._test_string(u'\nhyv\xe4\n') From bb165b10b998dbb219de5c311b859e080b20caf8 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 20 Dec 2013 09:43:35 +0000 Subject: [PATCH 107/214] [python3] utest: utils/test_xmlwriter: Reverted some explicit encoding args. --HG-- extra : transplant_source : %FE%2BD%B7%00%205%B3%5DV%A9%D1%A1%28%99%E4/G%C4D --- utest/utils/test_xmlwriter.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/utest/utils/test_xmlwriter.py b/utest/utils/test_xmlwriter.py index c0ab836571c..4eb212457ab 100644 --- a/utest/utils/test_xmlwriter.py +++ b/utest/utils/test_xmlwriter.py @@ -110,11 +110,11 @@ def test_custom_encoding(self): self.writer = XmlWriter(PATH, encoding=encoding) self.writer.element('test', u'hyv\xe4') self._verify_content('encoding="ISO-8859-1"', encoding=encoding) - self._verify_node(None, 'test', u'hyv\xe4', encoding=encoding) + self._verify_node(None, 'test', u'hyv\xe4') - def _verify_node(self, node, name, text=None, attrs={}, encoding='UTF-8'): + def _verify_node(self, node, name, text=None, attrs={}): if node is None: - node = self._get_root(encoding=encoding) + node = self._get_root() assert_equals(node.tag, name) if text is not None: assert_equals(node.text, text) @@ -125,9 +125,9 @@ def _verify_content(self, expected, encoding='UTF-8'): assert_true(expected in content, 'Failed to find:\n%s\n\nfrom:\n%s' % (expected, content)) - def _get_root(self, encoding='UTF-8'): + def _get_root(self): self.writer.close() - with ETSource(PATH, encoding=encoding) as source: + with ETSource(PATH) as source: return ET.parse(source).getroot() def _get_content(self, encoding='UTF-8'): From 3d9dddfa00efb16a8a06e0a237c8dd28c3492747 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 20 Dec 2013 10:41:24 +0000 Subject: [PATCH 108/214] [python3] utest: Compatible ImportError message checks. --HG-- extra : transplant_source : %19q%F0%60%83%FC%FC%5By%89%AD%D6%0B.%82j%29%89%2A%AC --- utest/running/test_testlibrary.py | 5 ++++- utest/utils/test_importer_util.py | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/utest/running/test_testlibrary.py b/utest/running/test_testlibrary.py index 8b369968eff..d632f46b676 100644 --- a/utest/running/test_testlibrary.py +++ b/utest/running/test_testlibrary.py @@ -1,6 +1,8 @@ import unittest import sys +PY3 = sys.version_info[0] == 3 + from robot.running.testlibraries import (TestLibrary, _ClassLibrary, _ModuleLibrary, _DynamicLibrary) from robot.utils.asserts import * @@ -79,7 +81,8 @@ def test_import_python_module_from_module(self): [("keyword from submodule", None)]) def test_import_non_existing_module(self): - msg = "Importing test library '%s' failed: ImportError: No module named %s" + msg = ("Importing test library '%s' failed: ImportError: No module named " + + "'%s'" if PY3 else "%s") for name in 'nonexisting', 'nonexi.sting': error = assert_raises(DataError, TestLibrary, name) assert_equals(unicode(error).splitlines()[0], diff --git a/utest/utils/test_importer_util.py b/utest/utils/test_importer_util.py index 8ea3dd724c2..e7b4232185e 100644 --- a/utest/utils/test_importer_util.py +++ b/utest/utils/test_importer_util.py @@ -8,6 +8,9 @@ import os import re from os.path import abspath, basename, dirname, exists, join, normpath +from itertools import repeat + +PY3 = sys.version_info[0] == 3 from robot.errors import DataError from robot.utils.importer import Importer, ByPathImporter @@ -377,7 +380,8 @@ def test_classpath(self): def test_structure(self): error = self._failing_import('NoneExisting') - message = "Importing 'NoneExisting' failed: ImportError: No module named NoneExisting" + message = ("Importing 'NoneExisting' failed: ImportError: No module named " + + ("'%s'" if PY3 else "%s") % 'NoneExisting') expected = (message, self._get_traceback(error), self._get_pythonpath(error), self._get_classpath(error)) assert_equals(unicode(error), '\n'.join(expected).strip()) From 5b91f63f4799974223c00770e3b4b5197e7d122d Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 20 Dec 2013 10:58:05 +0000 Subject: [PATCH 109/214] [python3] utest: Compatible .5 rounding checks. --HG-- extra : transplant_source : 5%BC%EE%0D%03%3CtR%25%1C%3E%10c%07%E4%D9%1A%DE%C0%0B --- utest/model/test_statistics.py | 6 +++++- utest/utils/test_robottime.py | 16 ++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/utest/model/test_statistics.py b/utest/model/test_statistics.py index 6be439c06c6..4604248b4cd 100644 --- a/utest/model/test_statistics.py +++ b/utest/model/test_statistics.py @@ -1,4 +1,7 @@ import unittest +import sys + +PY3 = sys.version_info[0] == 3 from robot.utils.asserts import assert_equals, assert_true from robot.model.statistics import Statistics @@ -202,7 +205,8 @@ def test_suite_stats_when_suite_has_no_times(self): def test_elapsed_from_get_attributes(self): for time, expected in [('00:00:00.000', '00:00:00'), ('00:00:00.001', '00:00:00'), - ('00:00:00.500', '00:00:01'), + ('00:00:00.500', '00:00:00' if PY3 + else '00:00:01'), ('00:00:00.999', '00:00:01'), ('00:00:01.000', '00:00:01'), ('00:00:01.001', '00:00:01'), diff --git a/utest/utils/test_robottime.py b/utest/utils/test_robottime.py index 0e2605c570a..7609102ae06 100644 --- a/utest/utils/test_robottime.py +++ b/utest/utils/test_robottime.py @@ -1,8 +1,11 @@ import unittest +import sys import re import time import datetime +PY3 = sys.version_info[0] == 3 + from robot.utils.asserts import (assert_equal, assert_raises_with_msg, assert_true, assert_not_none) @@ -176,7 +179,8 @@ def test_elapsed_time_to_string(self): for elapsed, expected in [(0, '00:00:00.000'), (0.1, '00:00:00.000'), (0.49999, '00:00:00.000'), - (0.5, '00:00:00.001'), + (0.5, '00:00:00.000' if PY3 + else '00:00:00.001'), (1, '00:00:00.001'), (42, '00:00:00.042'), (999, '00:00:00.999'), @@ -202,7 +206,8 @@ def test_elapsed_time_to_string_without_millis(self): (1, '00:00:00'), (499, '00:00:00'), (499.999, '00:00:00'), - (500, '00:00:01'), + (500, '00:00:00' if PY3 + else '00:00:01'), (999, '00:00:01'), (1000, '00:00:01'), (1499, '00:00:01'), @@ -211,12 +216,15 @@ def test_elapsed_time_to_string_without_millis(self): (59999, '00:01:00'), (60000, '00:01:00'), (654321, '00:10:54'), - (654500, '00:10:55'), + (654500, '00:10:54' if PY3 + else '00:10:55'), (3599999, '01:00:00'), (3600000, '01:00:00'), (359999999, '100:00:00'), (360000000, '100:00:00'), - (360000500, '100:00:01')]: + (360000500, '100:00:00' if PY3 + else '100:00:01'), + ]: assert_equal(elapsed_time_to_string(elapsed, include_millis=False), expected, elapsed) if expected != '00:00:00': From 2c1ace79eedca3eb5aaf7c6df9dc7329f4c013d0 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 20 Dec 2013 11:00:02 +0000 Subject: [PATCH 110/214] [python3] utest: test_unic: Exclude some tests in Python 3. --HG-- extra : transplant_source : %8B%98%24W%1E%FF%D4%FE%3C%CD%E0%FE%EBix%B5%1D6%08%89 --- utest/utils/test_unic.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/utest/utils/test_unic.py b/utest/utils/test_unic.py index 385db8684b7..c13f136bf7b 100644 --- a/utest/utils/test_unic.py +++ b/utest/utils/test_unic.py @@ -1,6 +1,8 @@ import unittest import sys +PY3 = sys.version_info[0] == 3 + from robot.utils import unic, safe_repr from robot.utils.asserts import assert_equals, assert_true @@ -72,7 +74,7 @@ def test_list_with_objects_containing_unicode_repr(self): def test_bytes_below_128(self): assert_equals(unic('\x00-\x01-\x02-\x7f'), u'\x00-\x01-\x02-\x7f') - if not IPY: + if not (IPY or PY3): def test_bytes_above_128(self): assert_equals(unic('hyv\xe4'), u'hyv\\xe4') @@ -107,14 +109,15 @@ def test_failure_in_repr(self): assert_equals(safe_repr(ReprFails()), UNREPR % ('ReprFails', 'Failure in __repr__')) - def test_repr_of_unicode_has_u_prefix(self): - assert_equals(safe_repr(u'foo'), "u'foo'") - assert_equals(safe_repr(u"f'o'o"), "u\"f'o'o\"") + if not PY3: + def test_repr_of_unicode_has_u_prefix(self): + assert_equals(safe_repr(u'foo'), "u'foo'") + assert_equals(safe_repr(u"f'o'o"), "u\"f'o'o\"") - def test_unicode_items_in_list_repr_have_u_prefix(self): - assert_equals(safe_repr([]), '[]') - assert_equals(safe_repr([u'foo']), "[u'foo']") - assert_equals(safe_repr([u'a', 1, u"'"]), "[u'a', 1, u\"'\"]") + def test_unicode_items_in_list_repr_have_u_prefix(self): + assert_equals(safe_repr([]), '[]') + assert_equals(safe_repr([u'foo']), "[u'foo']") + assert_equals(safe_repr([u'a', 1, u"'"]), "[u'a', 1, u\"'\"]") class UnicodeRepr(object): From 10398f7a8af2d10f1856505795c0c0a36d9216a0 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 20 Dec 2013 11:15:32 +0000 Subject: [PATCH 111/214] [python3] utest: test_variables: Compatible /-div result check. //-div tests. --HG-- extra : transplant_source : %2C%BB%BFe%B0%3Dw%E5%F1C%94%20%3D%97Zlh%7DY2 --- utest/variables/test_variables.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/utest/variables/test_variables.py b/utest/variables/test_variables.py index 710f279bc63..5b06abf0df3 100644 --- a/utest/variables/test_variables.py +++ b/utest/variables/test_variables.py @@ -1,6 +1,8 @@ import unittest import sys +PY3 = sys.version_info[0] == 3 + from robot.variables import variables, is_list_var, is_scalar_var, is_var from robot.errors import * from robot import utils @@ -267,19 +269,23 @@ def test_math_with_internal_vars(self): assert_equals(self.varz.replace_scalar('${${1}+${2}}'), 3) assert_equals(self.varz.replace_scalar('${${1}-${2}}'), -1) assert_equals(self.varz.replace_scalar('${${1}*${2}}'), 2) - assert_equals(self.varz.replace_scalar('${${1}/${2}}'), 0) + assert_equals(self.varz.replace_scalar('${${1}/${2}}'), + 0.5 if PY3 else 0) + assert_equals(self.varz.replace_scalar('${${1}//${2}}'), 0) def test_math_with_internal_vars_with_spaces(self): assert_equals(self.varz.replace_scalar('${${1} + ${2.5}}'), 3.5) assert_equals(self.varz.replace_scalar('${${1} - ${2} + 1}'), 0) assert_equals(self.varz.replace_scalar('${${1} * ${2} - 1}'), 1) assert_equals(self.varz.replace_scalar('${${1} / ${2.0}}'), 0.5) + assert_equals(self.varz.replace_scalar('${${1} // ${2.0}}'), 0.0) def test_math_with_internal_vars_does_not_work_if_first_var_is_float(self): assert_raises(DataError, self.varz.replace_scalar, '${${1.1}+${2}}') assert_raises(DataError, self.varz.replace_scalar, '${${1.1} - ${2}}') assert_raises(DataError, self.varz.replace_scalar, '${${1.1} * ${2}}') assert_raises(DataError, self.varz.replace_scalar, '${${1.1}/${2}}') + assert_raises(DataError, self.varz.replace_scalar, '${${1.1}//${2}}') def test_list_variable_as_scalar(self): self.varz['@{name}'] = exp = ['spam', 'eggs'] From 5ba974d0c1767a9a38fd207ac859a9e05413c483 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 20 Dec 2013 15:32:07 +0000 Subject: [PATCH 112/214] [python3] utest: test_jsonwriter: Don't encode expected string in Python 3. --HG-- extra : transplant_source : %DC%975%3A%C2%96%03%E6B%BD%25%1D%E7sD%9D%8A1f%91 --- utest/htmldata/test_jsonwriter.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/utest/htmldata/test_jsonwriter.py b/utest/htmldata/test_jsonwriter.py index f70d13ae803..d020611787f 100644 --- a/utest/htmldata/test_jsonwriter.py +++ b/utest/htmldata/test_jsonwriter.py @@ -1,3 +1,7 @@ +import sys + +PY3 = sys.version_info[0] == 3 + from StringIO import StringIO try: import json @@ -28,7 +32,10 @@ def test_dump_string(self): self._test('123', '"123"') def test_dump_non_ascii_string(self): - self._test(u'hyv\xe4', u'"hyv\xe4"'.encode('UTF-8')) + expected = u'"hyv\xe4"' + if not PY3: + expected = expected.encode('UTF-8') + self._test(u'hyv\xe4', expected) def test_escape_string(self): self._test('"-\\-\n-\t-\r', '"\\"-\\\\-\\n-\\t-\\r"') From da1d0794c6ce9418745fc43e3f9d2a49ca3f5fe3 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 20 Dec 2013 15:33:40 +0000 Subject: [PATCH 113/214] [python3] utest: test_error: raise without value if None. --HG-- extra : transplant_source : %29%ED%D8%C4%86%CF%9Dk%F4%FE%BC-%AE%ED%09%A6d%F4%88H --- utest/utils/test_error.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/utest/utils/test_error.py b/utest/utils/test_error.py index 9421d3938aa..e985c621cd7 100644 --- a/utest/utils/test_error.py +++ b/utest/utils/test_error.py @@ -24,6 +24,8 @@ def test_get_error_details_python(self): (AssertionError, 'Msg\nin 3\nlines', 'Msg\nin 3\nlines'), (ValueError, '2\nlines', 'ValueError: 2\nlines')]: try: + if not msg: + raise exception raise exception, msg except: message, details = get_error_details() From c89104719b017b4de39d01f75388934cbfb29e04 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 22 Dec 2013 14:00:25 +0000 Subject: [PATCH 114/214] [python3] _MarkupWriter: Little encoding fix. --HG-- extra : transplant_source : P%BA%0C%02%F3%1E%09%AABI_%DB%05%9C%E4%A7h%EF%ABS --- src/robot/utils/markupwriters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/robot/utils/markupwriters.py b/src/robot/utils/markupwriters.py index 2fc4e1de4ba..c173d0be7ca 100644 --- a/src/robot/utils/markupwriters.py +++ b/src/robot/utils/markupwriters.py @@ -36,7 +36,7 @@ def __init__(self, output, line_separator='\n', encoding='UTF-8'): output = open(output, 'w', encoding=encoding) else: output = open(output, 'w') - self._encode_output = not hasattr(output, 'encoding') + self._encode_output = encoding and not hasattr(output, 'encoding') self.output = output self._encoding = encoding self._line_separator = self._encode(line_separator) From f43d2a4006e208c6977e8b09cca739ec09c0c229 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 22 Dec 2013 14:07:41 +0000 Subject: [PATCH 115/214] [python3] utest: Some additional PY3 switches. --HG-- extra : transplant_source : A%E1%8554%F1j%15%9A%0D%F7P%8EX%80%B3%DA%13%A4%02 --- utest/result/test_resultserializer.py | 8 ++++++-- utest/running/test_testlibrary.py | 2 +- utest/utils/test_etreesource.py | 10 ++++++---- utest/utils/test_unic.py | 9 +++++---- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/utest/result/test_resultserializer.py b/utest/result/test_resultserializer.py index 8585ed522e1..12f2329de01 100644 --- a/utest/result/test_resultserializer.py +++ b/utest/result/test_resultserializer.py @@ -1,8 +1,12 @@ from __future__ import with_statement +import sys import unittest -try: + +PY3 = sys.version_info[0] == 3 + +if PY3: from io import StringIO, BytesIO -except ImportError: # Python < 3 +else: from StringIO import StringIO BytesIO = StringIO diff --git a/utest/running/test_testlibrary.py b/utest/running/test_testlibrary.py index d632f46b676..d971b71ff64 100644 --- a/utest/running/test_testlibrary.py +++ b/utest/running/test_testlibrary.py @@ -82,7 +82,7 @@ def test_import_python_module_from_module(self): def test_import_non_existing_module(self): msg = ("Importing test library '%s' failed: ImportError: No module named " - + "'%s'" if PY3 else "%s") + + ("'%s'" if PY3 else "%s")) for name in 'nonexisting', 'nonexi.sting': error = assert_raises(DataError, TestLibrary, name) assert_equals(unicode(error).splitlines()[0], diff --git a/utest/utils/test_etreesource.py b/utest/utils/test_etreesource.py index b03611ece4a..2e72596cc7c 100644 --- a/utest/utils/test_etreesource.py +++ b/utest/utils/test_etreesource.py @@ -3,13 +3,15 @@ import sys import unittest +PY3 = sys.version_info[0] == 3 + from robot.utils.asserts import assert_equals, assert_raises, assert_true from robot.utils.etreewrapper import ETSource, ET from robot.errors import DataError IRONPYTHON = sys.platform == 'cli' -PYTHON3 = sys.version_info[0] == 3 PATH = os.path.join(os.path.dirname(__file__), 'test_etreesource.py') +STARTSWITH = 'from __future__' if not PY3 else '\nimport os' class TestETSource(unittest.TestCase): @@ -20,7 +22,7 @@ def test_path_to_file(self): if IRONPYTHON: assert_equals(src, PATH) else: - assert_true(src.read().startswith('from __future__'.encode())) + assert_true(src.read().startswith(STARTSWITH.encode())) self._verify_string_representation(source, PATH) if IRONPYTHON: assert_true(source._opened is None) @@ -30,7 +32,7 @@ def test_path_to_file(self): def test_opened_file_object(self): source = ETSource(open(PATH)) with source as src: - assert_true(src.read().startswith('from __future__')) + assert_true(src.read().startswith(STARTSWITH)) assert_true(src.closed is False) self._verify_string_representation(source, PATH) assert_true(source._opened is None) @@ -45,7 +47,7 @@ def _test_string(self, xml): source = ETSource(xml) with source as src: content = src.read() - if not (IRONPYTHON or PYTHON3): + if not (IRONPYTHON or PY3): content = content.decode('UTF-8') assert_equals(content, xml) self._verify_string_representation(source, '') diff --git a/utest/utils/test_unic.py b/utest/utils/test_unic.py index c13f136bf7b..260b525ae36 100644 --- a/utest/utils/test_unic.py +++ b/utest/utils/test_unic.py @@ -64,7 +64,7 @@ def test_list_with_objects_containing_unicode_repr(self): if JYTHON: # This is actually wrong behavior assert_equals(result, '[Hyv\\xe4, Hyv\\xe4]') - elif IPY: + elif IPY or PY3: # And so is this. assert_equals(result, '[Hyv\xe4, Hyv\xe4]') else: @@ -94,9 +94,10 @@ def test_bytes_with_newlines_tabs_etc(self): # 'string_escape' escapes some chars we don't want to be escaped assert_equals(unic("\x00\xe4\n\t\r\\'"), u"\x00\xe4\n\t\r\\'") - def test_failure_in_unicode(self): - assert_equals(unic(UnicodeFails()), - UNREPR % ('UnicodeFails', 'Failure in __unicode__')) + if not PY3: + def test_failure_in_unicode(self): + assert_equals(unic(UnicodeFails()), + UNREPR % ('UnicodeFails', 'Failure in __unicode__')) def test_failure_in_str(self): assert_equals(unic(StrFails()), From 7cbd89ef6c996af0607667dd516ccd049b84d775 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 22 Dec 2013 16:03:13 +0000 Subject: [PATCH 116/214] [python3] _MarkupWriter: Additional PY3 switch. --HG-- extra : transplant_source : oIc7%0D%FB%14%04%01%60%D1%0A%BA%BFk%E0C%A0%F4%2B --- src/robot/utils/markupwriters.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/robot/utils/markupwriters.py b/src/robot/utils/markupwriters.py index c173d0be7ca..f4c6b46567b 100644 --- a/src/robot/utils/markupwriters.py +++ b/src/robot/utils/markupwriters.py @@ -36,7 +36,8 @@ def __init__(self, output, line_separator='\n', encoding='UTF-8'): output = open(output, 'w', encoding=encoding) else: output = open(output, 'w') - self._encode_output = encoding and not hasattr(output, 'encoding') + self._encode_output = encoding and not ( + PY3 and hasattr(output, 'encoding')) self.output = output self._encoding = encoding self._line_separator = self._encode(line_separator) From 7e81d16596a8a9bfc47aa3f2a4144f77a33ddab2 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 22 Dec 2013 19:08:52 +0000 Subject: [PATCH 117/214] [python3] Collections.Get Dictionary Keys: Added doc string info for disabled key sorting in Python 3. --HG-- extra : transplant_source : %B9%D8%5C%E0%0D%86%95%C3%BD%FA%85%A5p%1E%26%16%9A%ED%9E%E8 --- src/robot/libraries/Collections.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/robot/libraries/Collections.py b/src/robot/libraries/Collections.py index 7e0ea628636..b022008067c 100644 --- a/src/robot/libraries/Collections.py +++ b/src/robot/libraries/Collections.py @@ -516,6 +516,11 @@ def get_dictionary_keys(self, dictionary): `Keys` are returned in sorted order. The given `dictionary` is never altered by this keyword. + In *Python 3* keys are not sorted, + because most builtin types are not comparable to each other. + This issue needs a better solution in future releases... + Maybe imitate Python 2 sorting? Any suggestions? :) + Example: | ${keys} = | Get Dictionary Keys | ${D3} | => @@ -534,6 +539,9 @@ def get_dictionary_values(self, dictionary): Values are returned sorted according to keys. The given dictionary is never altered by this keyword. + In *Python 3* values are not sorted. + See `Get Dictionary Keys` for more details. + Example: | ${values} = | Get Dictionary Values | ${D3} | => @@ -547,6 +555,9 @@ def get_dictionary_items(self, dictionary): Items are returned sorted by keys. The given `dictionary` is not altered by this keyword. + In *Python 3* items are not sorted. + See `Get Dictionary Keys` for more details. + Example: | ${items} = | Get Dictionary Items | ${D3} | => From e9629dfde5f425d44880d91cb74cb125c9151f2f Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 22 Dec 2013 19:10:12 +0000 Subject: [PATCH 118/214] [python3] setup: Prepend fork description from README to long_description. --HG-- extra : transplant_source : %FB%F4%E7it%8A%02%9C%07t%24%00%3B%DA%94%0C%A6%9B%9AF --- setup.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 515d96770f4..445a1d032f2 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,6 @@ #!/usr/bin/env python +import re import sys import os from os.path import join, dirname @@ -11,8 +12,13 @@ with open(join(dirname(__file__), 'src', 'robot', 'version.py')) as py: exec(py.read()) +README = open(join(dirname(__file__), 'README.txt')).read() # Maximum width in Windows installer seems to be 70 characters -------| -DESCRIPTION = """ +DESCRIPTION = re.match( + r"(.|\n)*Robot Framework\n" + "===============\n\n", + README + ).group(0) + """ Robot Framework is a generic test automation framework for acceptance testing and acceptance test-driven development (ATDD). It has easy-to-use tabular test data syntax and utilizes the keyword-driven From e54ae6ea8a832367a8ee3d0a45d9141502152532 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 23 Jan 2014 11:29:45 +0000 Subject: [PATCH 119/214] [python3] Remote: Initial compatibility workarounds. --HG-- extra : transplant_source : %C5%2A%7E%15%C9%A49%CEn%02%B9%29%08k%17%0F%C1%21Xe --- src/robot/libraries/Remote.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/robot/libraries/Remote.py b/src/robot/libraries/Remote.py index cb543280474..d1c99551011 100644 --- a/src/robot/libraries/Remote.py +++ b/src/robot/libraries/Remote.py @@ -29,6 +29,8 @@ class ExpatError(Exception): IRONPYTHON = sys.platform == 'cli' +PY3 = sys.version_info[0] == 3 + class Remote(object): ROBOT_LIBRARY_SCOPE = 'TEST SUITE' @@ -45,8 +47,11 @@ def get_keyword_names(self, attempts=5): return self._client.get_keyword_names() except TypeError, err: time.sleep(1) + # To make err accessible after this except block in Python 3: + # (`err` will be deleted) + exc = err raise RuntimeError('Connecting remote server at %s failed: %s' - % (self._uri, err)) + % (self._uri, exc)) def get_keyword_arguments(self, name): try: @@ -97,13 +102,16 @@ def _handle_string(self, arg): def _contains_binary(self, arg): return (self.binary.search(arg) or - isinstance(arg, str) and not IRONPYTHON and + isinstance(arg, str) and not (PY3 or IRONPYTHON) and self.non_ascii.search(arg)) def _handle_binary(self, arg): try: - arg = str(arg) - except UnicodeError: + if PY3: + arg = bytes(map(ord, arg)) + else: + arg = str(arg) + except (ValueError, UnicodeError): raise ValueError('Cannot represent %r as binary.' % arg) return xmlrpclib.Binary(arg) From df27ee9cb1ca76e3d561f0a72e6620269514d74c Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 23 Jan 2014 11:31:22 +0000 Subject: [PATCH 120/214] [python3] atest: standard_libraries/remote: Compatibility workarounds. --HG-- extra : transplant_source : %04%B4%BB%17%AF%0C%D6e%BFPS%7C%C9%9Bt%80%E3l%A2%FF --- .../standard_libraries/remote/argument_coersion.txt | 12 ++++++------ .../standard_libraries/remote/binaryresult.py | 8 ++++++++ atest/testdata/standard_libraries/remote/invalid.txt | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/atest/testdata/standard_libraries/remote/argument_coersion.txt b/atest/testdata/standard_libraries/remote/argument_coersion.txt index b1f3fb3fb61..3aff19736c9 100644 --- a/atest/testdata/standard_libraries/remote/argument_coersion.txt +++ b/atest/testdata/standard_libraries/remote/argument_coersion.txt @@ -65,7 +65,7 @@ Custom object with non-ASCII representation MyObject(u'hyv\\xe4') u'hyv\\xe4' Custom object with binary representation - MyObject('\\x00\\x01') '\\x00\\x01' + MyObject('\\x00\\x01') '\\x00\\x01' binary=yes List \[] @@ -82,7 +82,7 @@ List with non-ASCII byte values \['\\x80', '\\xe4'] binary=yes List with binary values - \['\\x00', u'\\x01'] + \['\\x00', u'\\x01'] binary=yes Nested list \[['a', 'b'], 3, [[[4], True]]] @@ -92,7 +92,7 @@ List-like ('a', 'b', 'c') ['a', 'b', 'c'] ('One', -2, False, (None,), u'\\xe4') ['One', -2, False, [''], u'\\xe4'] set() [] - xrange(5) [0, 1, 2, 3, 4] + range(5) if __import__('sys').version_info[0] == 3 else xrange(5) [0, 1, 2, 3, 4] Dictionary {} @@ -115,18 +115,18 @@ Dictionary with non-ASCII byte keys and values {'\\xe4': '\\xe4'} {'\\\\xe4': '\\xe4'} binary=yes Dictionary with binary keys is not supported - [Documentation] FAIL TypeError: unhashable instance + [Documentation] FAIL REGEXP: TypeError: unhashable (instance|type: 'Binary') {'\\x00': 'value'} Dictionary with binary values - {0: '\\x00', 1: u'\\x01'} {'0': '\\x00', '1': '\\x01'} + {0: '\\x00', 1: u'\\x01'} {'0': '\\x00', '1': '\\x01'} binary=yes Nested dictionary {'a': 0, 'b': True, 'c': {'x': [1, 2, 3]}, '\\x7f': '\\x7f'} Mapping MyMapping() {} - MyMapping(a=1, b='\\x01') {'a': 1, 'b': '\\x01'} + MyMapping(a=1, b='\\x01') {'a': 1, 'b': '\\x01'} binary=yes MyMapping(a='one', b=2, c=[None, True]) {'a': 'one', 'b': 2, 'c': ['', True]} *** Keywords *** diff --git a/atest/testdata/standard_libraries/remote/binaryresult.py b/atest/testdata/standard_libraries/remote/binaryresult.py index 5ca46925ce5..f15a908070f 100644 --- a/atest/testdata/standard_libraries/remote/binaryresult.py +++ b/atest/testdata/standard_libraries/remote/binaryresult.py @@ -4,8 +4,14 @@ from remoteserver import DirectResultRemoteServer +PY3 = sys.version_info[0] == 3 + + class BinaryResult(object): + def blacheck(self, value): + raise RuntimeError((type(value), str(value))) + def return_binary(self, *ordinals): return self._result(return_=self._binary(ordinals)) @@ -32,6 +38,8 @@ def fail_binary(self, *ordinals): traceback=self._binary(ordinals, 'Traceback: ')) def _binary(self, ordinals, extra=''): + if PY3: + return Binary(bytes(map(ord, extra)) + bytes(map(int, ordinals))) return Binary(extra + ''.join(chr(int(o)) for o in ordinals)) def _result(self, return_='', output='', error='', traceback=''): diff --git a/atest/testdata/standard_libraries/remote/invalid.txt b/atest/testdata/standard_libraries/remote/invalid.txt index faa2df53782..7bad94aa41b 100644 --- a/atest/testdata/standard_libraries/remote/invalid.txt +++ b/atest/testdata/standard_libraries/remote/invalid.txt @@ -18,7 +18,7 @@ Invalid char in XML Invalid char in XML Exception - [Documentation] FAIL :my message + [Documentation] FAIL REGEXP: <(type 'exceptions.|class ')Exception'>:my message Exception my message Broken connection From 29a2d76d1854b67204b872ac8d3094cc422a7b5f Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 24 Jan 2014 16:48:03 +0000 Subject: [PATCH 121/214] [python3] utest: test_encoding: Check for bytes after encode if PY3. --HG-- extra : transplant_source : %22%0B%88G%8E8%C0%07%28o4%F7%19%9Ac%17%23%EDF%B7 --- utest/utils/test_encoding.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/utest/utils/test_encoding.py b/utest/utils/test_encoding.py index 3c96aa0369b..e8b0b418ac2 100644 --- a/utest/utils/test_encoding.py +++ b/utest/utils/test_encoding.py @@ -9,6 +9,8 @@ ENCODED = UNICODE.encode(OUTPUT_ENCODING) IRONPYTHON = sys.platform == 'cli' +PY3 = sys.version_info[0] == 3 + class TestDecodeOutput(unittest.TestCase): @@ -19,7 +21,7 @@ def test_return_unicode_as_is_by_default(self): if not IRONPYTHON: def test_decode(self): - assert isinstance(ENCODED, str) + assert isinstance(ENCODED, bytes if PY3 else str) assert_equals(decode_output(ENCODED), UNICODE) else: From 4c3e75cedba2d3248abf60b018211b0957f5b023 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sat, 1 Feb 2014 01:13:05 +0000 Subject: [PATCH 122/214] atest: standard_libraries/process: Some new compatibility workarounds. --HG-- extra : transplant_source : a%00%B2%C5a%0DG%F6%FD%F5%C0%3A%03%D3%E7%E8%E3x%91o --- .../process/newlines_and_encoding.txt | 6 +++--- .../process/run_process_with_timeout.txt | 8 ++++---- .../process/start_process_preferences.txt | 16 ++++++++-------- .../process/stdout_and_stderr.txt | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/atest/testdata/standard_libraries/process/newlines_and_encoding.txt b/atest/testdata/standard_libraries/process/newlines_and_encoding.txt index 930eba14cb4..2e5991fd36b 100644 --- a/atest/testdata/standard_libraries/process/newlines_and_encoding.txt +++ b/atest/testdata/standard_libraries/process/newlines_and_encoding.txt @@ -18,11 +18,11 @@ Non-ASCII in environment variables Result should equal ${result} stdout=True Trailing newline is removed - ${result}= Run Process python -c print('nothing to remove') + ${result}= Run Process python -c __import__('sys').stdout.write('nothing to remove') Result should equal ${result} stdout=nothing to remove - ${result}= Run Process python -c print('one is removed\\n') + ${result}= Run Process python -c __import__('sys').stdout.write('one is removed\\n') Result should equal ${result} stdout=one is removed - ${result}= Run Process python -c print('only one is removed\\n\\n\\n') + ${result}= Run Process python -c __import__('sys').stdout.write('only one is removed\\n\\n\\n') Result should equal ${result} stdout=only one is removed\n\n Internal newlines are preserved diff --git a/atest/testdata/standard_libraries/process/run_process_with_timeout.txt b/atest/testdata/standard_libraries/process/run_process_with_timeout.txt index 6ec4004f861..c8c194c5fdb 100644 --- a/atest/testdata/standard_libraries/process/run_process_with_timeout.txt +++ b/atest/testdata/standard_libraries/process/run_process_with_timeout.txt @@ -3,26 +3,26 @@ Resource resource.txt *** Test Cases *** Finish before timeout - ${result} = Run Process python -c print 'Hello, world!' timeout=10s + ${result} = Run Process python -c print('Hello, world!') timeout=10s Should Be Equal ${result.rc} ${0} Should Be Equal ${result.stdout} Hello, world! On timeout process is terminated by default [Setup] Check Precondition sys.version_info >= (2,6) - ${result} = Run Process python -c import time; time.sleep(1); print 'done' + ${result} = Run Process python -c import time; time.sleep(1); print('done') ... timeout=3ms stderr=STDOUT Should Not Be Equal ${result.rc} ${0} Should Be Equal ${result.stdout} ${EMPTY} On timeout process can be killed [Setup] Check Precondition sys.version_info >= (2,6) - ${result} = Run Process python -c import time; time.sleep(1); print 'done' + ${result} = Run Process python -c import time; time.sleep(1); print('done') ... timeout=0.002s on_timeout=kill stderr=STDOUT Should Not Be Equal ${result.rc} ${0} Should Be Equal ${result.stdout} ${EMPTY} On timeout process can be left running - ${result} = Run Process python -c import time; time.sleep(0.1); print 'done' + ${result} = Run Process python -c import time; time.sleep(0.1); print('done') ... timeout=0.001 alias=exceed on_timeout=CONTINUE Should Be Equal ${result} ${None} ${result} = Wait For Process handle=exceed diff --git a/atest/testdata/standard_libraries/process/start_process_preferences.txt b/atest/testdata/standard_libraries/process/start_process_preferences.txt index 52bcb3153ec..86cbc672e10 100644 --- a/atest/testdata/standard_libraries/process/start_process_preferences.txt +++ b/atest/testdata/standard_libraries/process/start_process_preferences.txt @@ -6,15 +6,15 @@ Resource resource.txt *** Test Cases *** Explicitly run Operating System library keyword - ${handle}= OperatingSystem.Start Process python -c "import os; print os.path.abspath(os.curdir);" + ${handle}= OperatingSystem.Start Process python -c "import os; print(os.path.abspath(os.curdir));" ${out}= Read Process Output Explicitly run Process library keyword - ${handle}= Process.Start Process python -c "import os; print os.path.abspath(os.curdir);" shell=True + ${handle}= Process.Start Process python -c "import os; print(os.path.abspath(os.curdir));" shell=True ${out}= Wait For Process Implicitly run Process library keyword - ${handle}= Start Process python -c "import os; print os.path.abspath(os.curdir);" shell=True + ${handle}= Start Process python -c "import os; print(os.path.abspath(os.curdir));" shell=True ${out}= Wait For Process ${out2}= Get Process Id # Should call CustomLib keyword Should Match ${out2} The Pid @@ -22,15 +22,15 @@ Implicitly run Process library keyword Implicitly run Operating System library keyword when library search order is set Set Library Search Order OperatingSystem - ${handle}= Start Process python -c "import os; print os.path.abspath(os.curdir);" + ${handle}= Start Process python -c "import os; print(os.path.abspath(os.curdir));" ${out}= Read Process Output [Teardown] Set Library Search Order ${EMPTY} Process switch - OperatingSystem.Start Process python -c "print 'hello'" alias=op1 - OperatingSystem.Start Process python -c "print 'hello'" alias=op2 - Start Process python -c "print 'hello'" shell=True alias=p1 - Start Process python -c "print 'hello'" shell=True alias=p2 + OperatingSystem.Start Process python -c "print('hello')" alias=op1 + OperatingSystem.Start Process python -c "print('hello')" alias=op2 + Start Process python -c "print('hello')" shell=True alias=p1 + Start Process python -c "print('hello')" shell=True alias=p2 Switch Process p1 Switch Process p2 OperatingSystem.Switch Process op1 diff --git a/atest/testdata/standard_libraries/process/stdout_and_stderr.txt b/atest/testdata/standard_libraries/process/stdout_and_stderr.txt index 4c91b1b14c2..d63784d7be2 100644 --- a/atest/testdata/standard_libraries/process/stdout_and_stderr.txt +++ b/atest/testdata/standard_libraries/process/stdout_and_stderr.txt @@ -47,7 +47,7 @@ Cwd does not affect absolute custom streams Lot of output to custom stream [Tags] performance - ${result}= Run Process python -c "for i in xrange(100000):\tprint 'a'*99" shell=True stdout=${STDOUT} + ${result}= Run Process python -c "for i in range(100000):\tprint('a'*99)" shell=True stdout=${STDOUT} Length Should Be ${result.stdout} 9999999 File Should Not Be Empty ${STDOUT} From c7acc61c5573a61b8192c1750a944740875c005d Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 10 Feb 2014 13:28:02 +0000 Subject: [PATCH 123/214] [python3] _DebugFileWriter._write(): Simpler PY3 handling. --HG-- extra : transplant_source : %18%17Q%C0N%C6M_H%AC%E7D%EDG%87j%AFc%BFW --- src/robot/output/debugfile.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/robot/output/debugfile.py b/src/robot/output/debugfile.py index 53dbbcaed75..ee3ed8eb84f 100644 --- a/src/robot/output/debugfile.py +++ b/src/robot/output/debugfile.py @@ -12,11 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys + from robot import utils from .logger import LOGGER from .loggerhelper import IsLogged +PY3 = sys.version_info[0] == 3 + def DebugFile(path): if not path: @@ -108,10 +112,9 @@ def _write(self, text, separator=False, level='INFO', timestamp=None): text = '%s - %s - %s' % (timestamp or utils.get_timestamp(), level, text) text = text.rstrip() + '\n' - encoded_text = text.encode('UTF-8') - try: - self._outfile.write(encoded_text) - except TypeError: # Python 3 + if PY3 and hasattr(self._outfile, 'encoding'): self._outfile.write(text) + else: + self._outfile.write(text.encode('UTF-8')) self._outfile.flush() self._separator_written_last = separator From 54fa58973cc80f456ac881be51d41f5473c2bb9b Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 11 Feb 2014 09:02:42 +0000 Subject: [PATCH 124/214] [python3] robot: Made code directly Python 2.7/3.3+ compatible by applying (only necessary) compatible 2to3 diffs and using six for other cases. --HG-- extra : transplant_source : %3D%A3%1C%14%60%28%E77/%B3g/%D9%1A%BB%A6%D4%28%93r --- atest/run_atests.py | 2 +- src/robot/conf/settings.py | 10 +-- src/robot/errors.py | 8 +-- src/robot/htmldata/htmlfilewriter.py | 1 - src/robot/htmldata/jartemplate.py | 1 - src/robot/htmldata/jsonwriter.py | 7 +- src/robot/htmldata/normaltemplate.py | 1 - src/robot/htmldata/testdata/create_jsdata.py | 2 + .../htmldata/testdata/create_libdoc_data.py | 2 +- .../htmldata/testdata/create_testdoc_data.py | 2 +- src/robot/jarrunner.py | 4 +- src/robot/libdoc.py | 3 - src/robot/libdocpkg/consoleviewer.py | 2 +- src/robot/libdocpkg/model.py | 2 - src/robot/libdocpkg/specbuilder.py | 1 - src/robot/libraries/BuiltIn.py | 64 ++++++++++--------- src/robot/libraries/Collections.py | 10 +-- src/robot/libraries/Dialogs.py | 4 +- src/robot/libraries/Easter.py | 2 +- src/robot/libraries/OperatingSystem.py | 9 +-- src/robot/libraries/Process.py | 4 +- src/robot/libraries/Remote.py | 26 ++++---- src/robot/libraries/Screenshot.py | 19 +++--- src/robot/libraries/String.py | 11 ++-- src/robot/libraries/Telnet.py | 11 ++-- src/robot/libraries/XML.py | 4 +- src/robot/libraries/dialogs_py.py | 10 +-- src/robot/model/criticality.py | 5 +- src/robot/model/filter.py | 5 +- src/robot/model/itemlist.py | 3 +- src/robot/model/message.py | 6 +- src/robot/model/metadata.py | 10 +-- src/robot/model/modelobject.py | 12 ++-- src/robot/model/namepatterns.py | 6 +- src/robot/model/stats.py | 9 ++- src/robot/model/tags.py | 16 +++-- src/robot/model/tagsetter.py | 8 ++- src/robot/output/debugfile.py | 6 +- src/robot/output/filelogger.py | 2 +- src/robot/output/listeners.py | 12 +++- src/robot/output/logger.py | 4 +- src/robot/output/loggerhelper.py | 2 + src/robot/output/stdoutlogsplitter.py | 2 +- src/robot/output/xmllogger.py | 4 +- src/robot/parsing/comments.py | 7 +- src/robot/parsing/datarow.py | 6 +- src/robot/parsing/htmlreader.py | 10 ++- src/robot/parsing/model.py | 22 +++++-- src/robot/parsing/populators.py | 6 +- src/robot/parsing/restreader.py | 21 +++--- src/robot/parsing/settings.py | 22 +++++-- src/robot/parsing/tablepopulators.py | 12 ++-- src/robot/reporting/jsbuildingcontext.py | 6 +- src/robot/reporting/jsexecutionresult.py | 2 +- src/robot/reporting/jsmodelbuilders.py | 2 - src/robot/reporting/jswriter.py | 2 +- src/robot/reporting/logreportwriters.py | 5 +- src/robot/reporting/resultwriter.py | 6 +- src/robot/reporting/stringcache.py | 13 ++-- src/robot/result/configurer.py | 4 +- src/robot/result/executionresult.py | 2 - src/robot/result/flattenkeywordmatcher.py | 4 +- src/robot/result/resultbuilder.py | 4 +- src/robot/result/testcase.py | 2 +- src/robot/run.py | 1 + .../running/arguments/argumentresolver.py | 4 +- src/robot/running/arguments/argumentspec.py | 2 +- .../running/arguments/javaargumentcoercer.py | 4 +- src/robot/running/builder.py | 4 +- src/robot/running/context.py | 2 +- src/robot/running/dynamicmethods.py | 12 +++- src/robot/running/handlers.py | 2 +- src/robot/running/importer.py | 6 +- src/robot/running/keywords.py | 24 ++++--- src/robot/running/model.py | 2 - src/robot/running/namespace.py | 14 ++-- src/robot/running/outputcapture.py | 7 +- src/robot/running/runkwregister.py | 4 +- src/robot/running/runner.py | 12 ++-- src/robot/running/signalhandler.py | 2 +- src/robot/running/status.py | 2 + src/robot/running/testlibraries.py | 1 - src/robot/running/timeouts/__init__.py | 12 +++- src/robot/running/timeouts/timeoutthread.py | 4 +- src/robot/running/timeouts/timeoutwin.py | 7 +- src/robot/running/userkeyword.py | 20 +++--- src/robot/testdoc.py | 7 +- src/robot/tidy.py | 8 ++- src/robot/utils/application.py | 13 ++-- src/robot/utils/argumentparser.py | 15 +++-- src/robot/utils/asserts.py | 9 ++- src/robot/utils/connectioncache.py | 18 ++++-- src/robot/utils/encoding.py | 6 +- src/robot/utils/error.py | 2 + src/robot/utils/escaping.py | 6 +- src/robot/utils/etreewrapper.py | 13 ++-- src/robot/utils/importer.py | 9 +-- src/robot/utils/islike.py | 10 +-- src/robot/utils/markupwriters.py | 7 +- src/robot/utils/match.py | 4 +- src/robot/utils/misc.py | 4 +- src/robot/utils/normalizing.py | 4 +- src/robot/utils/robotenv.py | 4 +- src/robot/utils/robotinspect.py | 2 +- src/robot/utils/robotpath.py | 11 +++- src/robot/utils/robottime.py | 6 +- src/robot/utils/unic.py | 2 + src/robot/utils/utf8reader.py | 4 +- src/robot/variables/isvar.py | 6 +- src/robot/variables/variableassigner.py | 14 ++-- src/robot/variables/variables.py | 18 +++--- src/robot/writer/datafilewriter.py | 5 +- src/robot/writer/filewriters.py | 4 +- utest/run_utests.py | 17 ++--- 114 files changed, 533 insertions(+), 335 deletions(-) diff --git a/atest/run_atests.py b/atest/run_atests.py index 5bb747ad0e6..21b2be7c641 100755 --- a/atest/run_atests.py +++ b/atest/run_atests.py @@ -63,7 +63,7 @@ ['2to3', '--no-diffs', '-n', '-w', '-x', 'dict', '-x', 'filter', - PY3DIR + PY3ATESTDIR ]) if status: sys.exit(status) diff --git a/src/robot/conf/settings.py b/src/robot/conf/settings.py index 70e6ec4e878..86cb3fd80e6 100644 --- a/src/robot/conf/settings.py +++ b/src/robot/conf/settings.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import string_types + import os from robot import utils @@ -71,7 +73,7 @@ def __init__(self, options=None, **extra_options): def _process_cli_opts(self, opts): for name, (cli_name, default) in self._cli_opts.items(): value = opts[cli_name] if cli_name in opts else default - if default == [] and isinstance(value, basestring): + if default == [] and isinstance(value, string_types): value = [value] self[name] = self._process_value(name, value) self['TestNames'] += self['ReRunFailed'] or self['DeprecatedRunFailed'] @@ -220,7 +222,7 @@ def _create_output_dir(self, path, type_): try: if not os.path.exists(path): os.makedirs(path) - except EnvironmentError, err: + except EnvironmentError as err: raise DataError("Creating %s file directory '%s' failed: %s" % (type_.lower(), path, err.strerror)) @@ -292,14 +294,14 @@ def _validate_remove_keywords(self, values): for value in values: try: KeywordRemover(value) - except DataError, err: + except DataError as err: raise DataError("Invalid value for option '--removekeywords'. %s" % err) def _validate_flatten_keywords(self, values): for value in values: try: FlattenKeywordMatcher(value) - except DataError, err: + except DataError as err: raise DataError("Invalid value for option '--flattenkeywords'. %s" % err) def __contains__(self, setting): diff --git a/src/robot/errors.py b/src/robot/errors.py index 1a5ff0e45a7..d2d4dd6fa38 100644 --- a/src/robot/errors.py +++ b/src/robot/errors.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import text_type as unicode + ##TODO: In Python 3 this causes some circular import problems: ## import utils @@ -35,11 +37,7 @@ def __init__(self, message='', details=''): @property def message(self): - return self.__unicode__() - - def __unicode__(self): - # Needed to handle exceptions w/ Unicode correctly on Python 2.5 - return unicode(self.args[0]) if self.args else u'' + return unicode(self) class FrameworkError(RobotError): diff --git a/src/robot/htmldata/htmlfilewriter.py b/src/robot/htmldata/htmlfilewriter.py index aa3f4c017f5..1dc862b9161 100644 --- a/src/robot/htmldata/htmlfilewriter.py +++ b/src/robot/htmldata/htmlfilewriter.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement import os.path import re diff --git a/src/robot/htmldata/jartemplate.py b/src/robot/htmldata/jartemplate.py index 9c6e18f619c..652cbfd9a46 100644 --- a/src/robot/htmldata/jartemplate.py +++ b/src/robot/htmldata/jartemplate.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement import os from posixpath import normpath, join from contextlib import contextmanager diff --git a/src/robot/htmldata/jsonwriter.py b/src/robot/htmldata/jsonwriter.py index 5319d157f81..605afda3e14 100644 --- a/src/robot/htmldata/jsonwriter.py +++ b/src/robot/htmldata/jsonwriter.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3, integer_types import sys @@ -76,7 +77,7 @@ def dump(self, data, mapping): class StringDumper(_Dumper): - _handled_types = basestring + _handled_types = str if PY3 else basestring _search_and_replace = [('\\', '\\\\'), ('"', '\\"'), ('\t', '\\t'), ('\n', '\\n'), ('\r', '\\r'), ('= 0: prefix = '0b' @@ -112,7 +114,7 @@ def _handle_java_numbers(self, item): return item def _get_base(self, item, base): - if not isinstance(item, basestring): + if not isinstance(item, string_types): return item, base item = utils.normalize(item) if item.startswith(('-', '+')): @@ -208,9 +210,9 @@ def _convert_to_bin_oct_hex(self, method, item, base, prefix, length, if ret[0] == '-': prefix = '-' + prefix ret = ret[1:] - if len(ret) > 1: # oct(0) -> '0' (i.e. has no prefix) + if len(ret) > 1: # oct(0) -> '0' (i.e. has no prefix), PY3: '0o0' prefix_length = {bin: 2, - oct: (2 if sys.version_info[0] == 3 else 1), + oct: (2 if PY3 else 1), # PY3: 0o..., PY2: 0... hex: 2 }[method] ret = ret[prefix_length:] @@ -293,7 +295,7 @@ def convert_to_boolean(self, item): using Python's `bool` method. """ self._log_types(item) - if isinstance(item, basestring): + if isinstance(item, string_types): if utils.eq(item, 'True'): return True if utils.eq(item, 'False'): @@ -346,7 +348,7 @@ def convert_to_bytes(self, input, input_type='text'): New in Robot Framework 2.8.2. """ try: - if sys.version_info[0] == 3 and input_type in ('text', 'int') and ( + if PY3 and input_type in ('text', 'int') and ( isinstance(input, (bytes, bytearray)) ): return bytes(input) @@ -354,7 +356,7 @@ def convert_to_bytes(self, input, input_type='text'): ordinals = getattr(self, '_get_ordinals_from_%s' % input_type) except AttributeError: raise RuntimeError("Invalid input type '%s'." % input_type) - if sys.version_info[0] == 3: + if PY3: # bytes directly take number sequences return bytes(ordinals(input)) return ''.join(chr(o) for o in ordinals(input)) except: @@ -372,9 +374,9 @@ def _test_ordinal(self, ordinal, original, type): % (type, original)) def _get_ordinals_from_int(self, input): - if isinstance(input, basestring): + if isinstance(input, string_types): input = input.split() - elif isinstance(input, (int, long)): + elif isinstance(input, integer_types): input = [input] for integer in input: ordinal = self._convert_to_integer(integer) @@ -391,12 +393,12 @@ def _get_ordinals_from_bin(self, input): yield self._test_ordinal(ordinal, token, 'Binary value') def _input_to_tokens(self, input, length): - if not isinstance(input, basestring): + if not isinstance(input, string_types): return input input = ''.join(input.split()) if len(input) % length != 0: raise RuntimeError('Expected input to be multiple of %d.' % length) - return (input[i:i+length] for i in xrange(0, len(input), length)) + return (input[i:i+length] for i in range(0, len(input), length)) def create_list(self, *items): """Returns a list containing given items. @@ -528,12 +530,12 @@ def _log_types(self, *args): def _get_type(self, arg): # In IronPython type(u'x') is str. We want to report unicode anyway, # except for Python 3. - if sys.version_info[0] < 3 and isinstance(arg, unicode): + if PY2 and isinstance(arg, unicode): return "" return str(type(arg)) def _include_values(self, values): - if isinstance(values, basestring): + if isinstance(values, string_types): return values.lower() not in ['no values', 'false'] return bool(values) @@ -645,7 +647,7 @@ def should_not_be_equal_as_strings(self, first, second, msg=None, values=True): error message with `msg` and `values`. """ self._log_types(first, second) - first, second = [self._convert_to_string(i) for i in first, second] + first, second = [self._convert_to_string(i) for i in (first, second)] self._should_not_be_equal(first, second, msg, values) def should_be_equal_as_strings(self, first, second, msg=None, values=True): @@ -655,7 +657,7 @@ def should_be_equal_as_strings(self, first, second, msg=None, values=True): error message with `msg` and `values`. """ self._log_types(first, second) - first, second = [self._convert_to_string(i) for i in first, second] + first, second = [self._convert_to_string(i) for i in (first, second)] self._should_be_equal(first, second, msg, values) def should_not_start_with(self, str1, str2, msg=None, values=True): @@ -1161,7 +1163,7 @@ def _resolve_possible_variable(self, name): return name def _unescape_variable_if_needed(self, name): - if not (isinstance(name, basestring) and len(name) > 1): + if not (isinstance(name, string_types) and len(name) > 1): raise ValueError if name.startswith('\\'): name = name[1:] @@ -1202,7 +1204,7 @@ def run_keyword(self, name, *args): can be a variable and thus set dynamically, e.g. from a return value of another keyword or from the command line. """ - if not isinstance(name, basestring): + if not isinstance(name, string_types): raise RuntimeError('Keyword name must be a string.') kw = Keyword(name, list(args)) return kw.run(self._context) @@ -1244,10 +1246,10 @@ def run_keywords(self, *keywords): for kw, args in self._split_run_keywords(list(keywords)): try: self.run_keyword(kw, *args) - except ExecutionPassed, err: + except ExecutionPassed as err: err.set_earlier_failures(errors) raise err - except ExecutionFailed, err: + except ExecutionFailed as err: errors.extend(err.get_errors()) if not err.can_continue(self._context.in_teardown): break @@ -1375,7 +1377,7 @@ def run_keyword_and_ignore_error(self, name, *args): """ try: return 'PASS', self.run_keyword(name, *args) - except ExecutionFailed, err: + except ExecutionFailed as err: if err.dont_continue: raise return 'FAIL', unicode(err) @@ -1414,7 +1416,7 @@ def run_keyword_and_continue_on_failure(self, name, *args): """ try: return self.run_keyword(name, *args) - except ExecutionFailed, err: + except ExecutionFailed as err: if not err.dont_continue: err.continue_on_failure = True raise err @@ -1442,7 +1444,7 @@ def run_keyword_and_expect_error(self, expected_error, name, *args): """ try: self.run_keyword(name, *args) - except ExecutionFailed, err: + except ExecutionFailed as err: if err.dont_continue: raise # To make err accessible after this except block in Python 3: @@ -1482,7 +1484,7 @@ def repeat_keyword(self, times, name, *args): times = self._convert_to_integer(times) if times <= 0: self.log("Keyword '%s' repeated zero times" % name) - for i in xrange(times): + for i in range(times): self.log("Repeating keyword, round %d/%d" % (i+1, times)) self.run_keyword(name, *args) @@ -1517,7 +1519,7 @@ def wait_until_keyword_succeeds(self, timeout, retry_interval, name, *args): while not error: try: return self.run_keyword(name, *args) - except ExecutionFailed, err: + except ExecutionFailed as err: if err.dont_continue: raise if time.time() > maxtime: @@ -2141,7 +2143,7 @@ def set_log_level(self, level): """ try: old = self._context.output.set_log_level(level) - except DataError, err: + except DataError as err: raise RuntimeError(unicode(err)) self._namespace.variables.set_global('${LOG_LEVEL}', level.upper()) self.log('Log level changed from %s to %s' % (old, level.upper())) @@ -2173,7 +2175,7 @@ def import_library(self, name, *args): """ try: self._namespace.import_library(name, list(args)) - except DataError, err: + except DataError as err: raise RuntimeError(unicode(err)) @run_keyword_variant(resolve=0) @@ -2197,7 +2199,7 @@ def import_variables(self, path, *args): """ try: self._namespace.import_variables(path, list(args), overwrite=True) - except DataError, err: + except DataError as err: raise RuntimeError(unicode(err)) @run_keyword_variant(resolve=0) @@ -2217,7 +2219,7 @@ def import_resource(self, path): """ try: self._namespace.import_resource(path) - except DataError, err: + except DataError as err: raise RuntimeError(unicode(err)) def set_library_search_order(self, *libraries): @@ -2280,7 +2282,7 @@ def keyword_should_exist(self, name, msg=None): raise DataError("No keyword with name '%s' found." % name) if isinstance(handler, UserErrorHandler): handler.run() - except DataError, err: + except DataError as err: raise AssertionError(msg or unicode(err)) def get_time(self, format='timestamp', time_='NOW'): @@ -2638,7 +2640,7 @@ def get_library_instance(self, name): """ try: return self._namespace.get_library_instance(name) - except DataError, err: + except DataError as err: raise RuntimeError(unicode(err)) @@ -2683,7 +2685,7 @@ def _matches(self, string, pattern): return matcher.match(string) def _is_true(self, condition): - if isinstance(condition, basestring): + if isinstance(condition, string_types): condition = self.evaluate(condition, modules='os,sys') return bool(condition) diff --git a/src/robot/libraries/Collections.py b/src/robot/libraries/Collections.py index 645223d5b7f..9a29076fd92 100644 --- a/src/robot/libraries/Collections.py +++ b/src/robot/libraries/Collections.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3, string_types, text_type as unicode + import sys from robot.api import logger @@ -377,7 +379,7 @@ def _yield_list_diffs(self, list1, list2, names): name = ' (%s)' % names[index] if index in names else '' try: assert_equals(item1, item2, msg='Index %d%s' % (index, name)) - except AssertionError, err: + except AssertionError as err: yield unic(err) def list_should_contain_sub_list(self, list1, list2, msg=None, values=True): @@ -529,7 +531,7 @@ def get_dictionary_keys(self, dictionary): #TODO: Sorting causes problems when key types are not comparable, # especially in Python 3 where even basic types like int and str # are not comparable to each other. - if sys.version_info[0] == 3: + if PY3: return list(dictionary) return sorted(dictionary) @@ -710,7 +712,7 @@ def _yield_dict_diffs(self, keys, dict1, dict2): for key in keys: try: assert_equals(dict1[key], dict2[key], msg='Key %s' % (key,)) - except AssertionError, err: + except AssertionError as err: yield unic(err) @@ -772,6 +774,6 @@ def _verify_condition(condition, default_msg, given_msg, include_default=False): raise AssertionError(given_msg) def _include_default_message(include): - if isinstance(include, basestring): + if isinstance(include, string_types): return include.lower() not in ['no values', 'false'] return bool(include) diff --git a/src/robot/libraries/Dialogs.py b/src/robot/libraries/Dialogs.py index 90037fa43d9..ef0bed43504 100644 --- a/src/robot/libraries/Dialogs.py +++ b/src/robot/libraries/Dialogs.py @@ -31,9 +31,9 @@ import sys if sys.platform.startswith('java'): - from dialogs_jy import MessageDialog, PassFailDialog, InputDialog, SelectionDialog + from .dialogs_jy import MessageDialog, PassFailDialog, InputDialog, SelectionDialog elif sys.platform == 'cli': - from dialogs_ipy import MessageDialog, PassFailDialog, InputDialog, SelectionDialog + from .dialogs_ipy import MessageDialog, PassFailDialog, InputDialog, SelectionDialog else: ## from dialogs_py import MessageDialog, PassFailDialog, InputDialog, SelectionDialog #HACK: Prevent 2to3 from converting to relative import diff --git a/src/robot/libraries/Easter.py b/src/robot/libraries/Easter.py index 3254a41edc3..999f94290d4 100644 --- a/src/robot/libraries/Easter.py +++ b/src/robot/libraries/Easter.py @@ -15,4 +15,4 @@ def none_shall_pass(who): if who is not None: raise AssertionError('None shall pass!') - print '*HTML* ' + print('*HTML* ') diff --git a/src/robot/libraries/OperatingSystem.py b/src/robot/libraries/OperatingSystem.py index d27cbe6c377..5cb4a5aea76 100644 --- a/src/robot/libraries/OperatingSystem.py +++ b/src/robot/libraries/OperatingSystem.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement +from six import PY3, text_type as unicode + import os import sys import tempfile @@ -1252,7 +1253,7 @@ def set_modified_time(self, path, mtime): if not os.path.isfile(path): raise ValueError('Modified time can only be set to regular files') mtime = parse_time(mtime) - except ValueError, err: + except ValueError as err: raise RuntimeError("Setting modified time of '%s' failed: %s" % (path, unicode(err))) os.utime(path, (mtime, mtime)) @@ -1400,7 +1401,7 @@ def _log(self, msg, level): if logger: logger.write(msg, level) else: - print '*%s* %s' % (level, msg) + print('*%s* %s' % (level, msg)) class _Process: @@ -1438,7 +1439,7 @@ def _process_command(self, command): command = command[:-1] + ' 2>&1 &' else: command += ' 2>&1' - if sys.version_info[0] == 3: + if PY3: return command return self._encode_to_file_system(command) diff --git a/src/robot/libraries/Process.py b/src/robot/libraries/Process.py index 30edaf42f53..ce898a808c3 100644 --- a/src/robot/libraries/Process.py +++ b/src/robot/libraries/Process.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement +from six import string_types import sys import os @@ -806,6 +806,6 @@ def __str__(self): def is_true(argument): - if isinstance(argument, basestring) and argument.upper() == 'FALSE': + if isinstance(argument, string_types) and argument.upper() == 'FALSE': return False return bool(argument) diff --git a/src/robot/libraries/Remote.py b/src/robot/libraries/Remote.py index 1e2cc6c4250..389093ae79a 100644 --- a/src/robot/libraries/Remote.py +++ b/src/robot/libraries/Remote.py @@ -12,11 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3, integer_types, string_types + import re import socket import sys import time -import xmlrpclib +if PY3: + import xmlrpc.client as xmlrpclib +else: + import xmlrpclib try: from xml.parsers.expat import ExpatError except ImportError: # No expat in IronPython 2.7 @@ -29,8 +34,6 @@ class ExpatError(Exception): IRONPYTHON = sys.platform == 'cli' -PY3 = sys.version_info[0] == 3 - class Remote(object): ROBOT_LIBRARY_SCOPE = 'TEST SUITE' @@ -45,7 +48,7 @@ def get_keyword_names(self, attempts=5): for i in range(attempts): try: return self._client.get_keyword_names() - except TypeError, err: + except TypeError as err: time.sleep(1) # To make err accessible after this except block in Python 3: # (`err` will be deleted) @@ -91,10 +94,10 @@ def coerce(self, argument): return handle(argument) def _is_string(self, arg): - return isinstance(arg, basestring) + return isinstance(arg, string_types) def _is_number(self, arg): - return isinstance(arg, (int, long, float)) + return isinstance(arg, integer_types + (float,)) def _handle_string(self, arg): if self._contains_binary(arg): @@ -178,9 +181,10 @@ def __init__(self, uri): def get_keyword_names(self): try: return self._server.get_keyword_names() - except socket.error, (errno, err): + except socket.error as err: + errno, err = err.args raise TypeError(err) - except xmlrpclib.Error, err: + except xmlrpclib.Error as err: raise TypeError(err) def get_keyword_arguments(self, name): @@ -199,11 +203,11 @@ def run_keyword(self, name, args, kwargs): run_keyword_args = [name, args, kwargs] if kwargs else [name, args] try: return self._server.run_keyword(*run_keyword_args) - except xmlrpclib.Fault, err: + except xmlrpclib.Fault as err: message = err.faultString - except socket.error, err: + except socket.error as err: message = 'Connection to remote server broken: %s' % err - except ExpatError, err: + except ExpatError as err: message = ('Processing XML-RPC return value failed. ' 'Most often this happens when the return value ' 'contains characters that are not valid in XML. ' diff --git a/src/robot/libraries/Screenshot.py b/src/robot/libraries/Screenshot.py index 1c0cd5caf0d..77872bb761c 100644 --- a/src/robot/libraries/Screenshot.py +++ b/src/robot/libraries/Screenshot.py @@ -253,24 +253,27 @@ def __init__(self, module_name=None): def __call__(self, path): self._screenshot(path) - def __nonzero__(self): + def __bool__(self): return self.module != 'no' + def __nonzero__(self): + return self.__bool__() + def test(self, path=None): - print "Using '%s' module." % self.module + print("Using '%s' module." % self.module) if not self: return False if not path: - print "Not taking test screenshot." + print("Not taking test screenshot.") return True - print "Taking test screenshot to '%s'." % path + print("Taking test screenshot to '%s'." % path) try: self(path) except: - print "Failed: %s" % utils.get_error_message() + print("Failed: %s" % utils.get_error_message()) return False else: - print "Success!" + print("Success!") return True def _get_screenshot_taker(self, module_name): @@ -348,6 +351,6 @@ def _no_screenshot(self, path): path = utils.abspath(sys.argv[1]) module = sys.argv[2] if len(sys.argv) == 3 else None shooter = ScreenshotTaker(module) - print 'Using %s modules' % shooter.module + print('Using %s modules' % shooter.module) shooter(path) - print path + print(path) diff --git a/src/robot/libraries/String.py b/src/robot/libraries/String.py index addb627d25f..cc7ad5dd3cb 100644 --- a/src/robot/libraries/String.py +++ b/src/robot/libraries/String.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import string_types, text_type as unicode + import sys import re from fnmatch import fnmatchcase @@ -418,7 +420,7 @@ def generate_random_string(self, length=8, chars='[LETTERS][NUMBERS]'): ('[NUMBERS]', digits)]: chars = chars.replace(name, value) maxi = len(chars) - 1 - return ''.join(chars[randint(0, maxi)] for _ in xrange(length)) + return ''.join(chars[randint(0, maxi)] for _ in range(length)) def get_substring(self, string, start, end=None): """Returns a substring from `start` index to `end` index. @@ -448,7 +450,7 @@ def should_be_string(self, item, msg=None): The default error message can be overridden with the optional `msg` argument. """ - if not isinstance(item, basestring): + if not isinstance(item, string_types): self._fail(msg, "'%s' is not a string.", item) def should_not_be_string(self, item, msg=None): @@ -457,7 +459,7 @@ def should_not_be_string(self, item, msg=None): The default error message can be overridden with the optional `msg` argument. """ - if isinstance(item, basestring): + if isinstance(item, string_types): self._fail(msg, "'%s' is a string.", item) def should_be_unicode_string(self, item, msg=None): @@ -487,7 +489,8 @@ def should_be_byte_string(self, item, msg=None): New in Robot Framework 2.7.7. """ - if not isinstance(item, bytes if sys.version_info[0] == 3 else str): + # Python 2.7 also has 'bytes' (alias for 'str') + if not isinstance(item, bytes): self._fail(msg, "'%s' is not a byte string.", item) def should_be_lowercase(self, string, msg=None): diff --git a/src/robot/libraries/Telnet.py b/src/robot/libraries/Telnet.py index 56c506d2d36..f1f3d6a06ca 100644 --- a/src/robot/libraries/Telnet.py +++ b/src/robot/libraries/Telnet.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement +from six import string_types, text_type as unicode + from contextlib import contextmanager import telnetlib import time @@ -380,7 +381,7 @@ def _get_terminal_emulation_with_default(self, terminal_emulation): def _parse_terminal_emulation(self, terminal_emulation): if not terminal_emulation: return False - if isinstance(terminal_emulation, basestring): + if isinstance(terminal_emulation, string_types): return terminal_emulation.lower() == 'true' return bool(terminal_emulation) @@ -624,7 +625,7 @@ def _set_default_log_level(self, level): def _is_valid_log_level(self, level): if level is None: return True - if not isinstance(level, basestring): + if not isinstance(level, string_types): return False return level.upper() in ('TRACE', 'DEBUG', 'INFO', 'WARN') @@ -891,7 +892,7 @@ def read_until_regexp(self, *expected): success, output = self._read_until_regexp(*expected) self._log(output, loglevel) if not success: - expected = [exp if isinstance(exp, basestring) else exp.pattern + expected = [exp if isinstance(exp, string_types) else exp.pattern for exp in expected] raise NoMatchError(expected, self._timeout, output) return output @@ -1092,7 +1093,7 @@ def __init__(self, expected, timeout, output=None): def _get_message(self): expected = "'%s'" % self.expected \ - if isinstance(self.expected, basestring) \ + if isinstance(self.expected, string_types) \ else utils.seq2str(self.expected, lastsep=' or ') msg = "No match found for %s in %s." % (expected, self.timeout) if self.output is not None: diff --git a/src/robot/libraries/XML.py b/src/robot/libraries/XML.py index f9a3254c508..e3efd2d08b0 100644 --- a/src/robot/libraries/XML.py +++ b/src/robot/libraries/XML.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement +from six import string_types import copy import re @@ -460,7 +460,7 @@ def get_elements(self, source, xpath): | ${children} = | Get Elements | ${XML} | first/child | | Should Be Empty | ${children} | | | """ - if isinstance(source, basestring): + if isinstance(source, string_types): source = self.parse_xml(source) if not xpath: raise RuntimeError('No xpath given.') diff --git a/src/robot/libraries/dialogs_py.py b/src/robot/libraries/dialogs_py.py index 17ea8365b81..84d016fec70 100644 --- a/src/robot/libraries/dialogs_py.py +++ b/src/robot/libraries/dialogs_py.py @@ -12,14 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3 + import sys from threading import currentThread -try: - from Tkinter import (Tk, Toplevel, Frame, Listbox, Label, Button, Entry, - BOTH, END, LEFT, W) -except ImportError: # Python 3 +if PY3: from tkinter import (Tk, Toplevel, Frame, Listbox, Label, Button, Entry, BOTH, END, LEFT, W) +else: + from Tkinter import (Tk, Toplevel, Frame, Listbox, Label, Button, Entry, + BOTH, END, LEFT, W) class _TkDialog(Toplevel): diff --git a/src/robot/model/criticality.py b/src/robot/model/criticality.py index 380149b16ca..3f77573c21f 100644 --- a/src/robot/model/criticality.py +++ b/src/robot/model/criticality.py @@ -35,5 +35,8 @@ def test_is_critical(self, test): return False return not self.non_critical_tags.match(test.tags) - def __nonzero__(self): + def __bool__(self): return bool(self.critical_tags or self.non_critical_tags) + + def __nonzero__(self): + return self.__bool__() diff --git a/src/robot/model/filter.py b/src/robot/model/filter.py index 38c2d34846d..543517e3aa9 100644 --- a/src/robot/model/filter.py +++ b/src/robot/model/filter.py @@ -95,6 +95,9 @@ def _included_by_tags(self, test): def _not_excluded_by_tags(self, test): return not self.exclude_tags.match(test.tags) - def __nonzero__(self): + def __bool__(self): return bool(self.include_suites or self.include_tests or self.include_tags or self.exclude_tags) + + def __nonzero__(self): + return self.__bool__() diff --git a/src/robot/model/itemlist.py b/src/robot/model/itemlist.py index 21a14dd17b5..715be5cd540 100644 --- a/src/robot/model/itemlist.py +++ b/src/robot/model/itemlist.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3, text_type as unicode import sys @@ -86,6 +87,6 @@ def __unicode__(self): return u'[%s]' % ', '.join(unicode(item) for item in self) def __str__(self): - if sys.version_info[0] == 3: + if PY3: return self.__unicode__() return unicode(self).encode('ASCII', 'replace') diff --git a/src/robot/model/message.py b/src/robot/model/message.py index 79b9254cb51..b48aeda0398 100644 --- a/src/robot/model/message.py +++ b/src/robot/model/message.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3 + import sys from robot.utils import html_escape @@ -53,9 +55,9 @@ def visit(self, visitor): def __unicode__(self): return self.message - if sys.version_info[0] == 3: + if PY3: def __str__(self): - return self.message + return self.__unicode__() class Messages(ItemList): diff --git a/src/robot/model/metadata.py b/src/robot/model/metadata.py index a169db0b3da..23f6da6cae7 100644 --- a/src/robot/model/metadata.py +++ b/src/robot/model/metadata.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3 + import sys from robot.utils import NormalizedDict @@ -26,10 +28,10 @@ def __unicode__(self): return u'{%s}' % ', '.join('%s: %s' % (k, self[k]) for k in self) def __str__(self): - if sys.version_info[0] == 3: + if PY3: return self.__unicode__() return unicode(self).encode('ASCII', 'replace') - if sys.version_info[0] == 3: - def __bytes__(self): - return str(self).encode('ASCII', 'replace') + #PY3 + def __bytes__(self): + return str(self).encode('ASCII', 'replace') diff --git a/src/robot/model/modelobject.py b/src/robot/model/modelobject.py index 876a08ba4fd..b637a00ce64 100644 --- a/src/robot/model/modelobject.py +++ b/src/robot/model/modelobject.py @@ -12,26 +12,28 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3, add_metaclass + import sys from robot.utils.setter import SetterAwareType +@add_metaclass(SetterAwareType) class ModelObject(object): __slots__ = [] - __metaclass__ = SetterAwareType def __unicode__(self): return self.name def __str__(self): - if sys.version_info[0] == 3: + if PY3: return self.__unicode__() return unicode(self).encode('ASCII', 'replace') - if sys.version_info[0] == 3: - def __bytes__(self): - return str(self).encode('ASCII', 'replace') + # PY3 + def __bytes__(self): + return str(self).encode('ASCII', 'replace') def __repr__(self): return repr(str(self)) diff --git a/src/robot/model/namepatterns.py b/src/robot/model/namepatterns.py index 26b6df005c9..8aafb0e0d5e 100644 --- a/src/robot/model/namepatterns.py +++ b/src/robot/model/namepatterns.py @@ -29,9 +29,13 @@ def _match(self, name): def _match_longname(self, name): raise NotImplementedError - def __nonzero__(self): + def __bool__(self): return bool(self._matcher) + #PY2 + def __nonzero__(self): + return self.__bool__() + def __iter__(self): return iter(self._matcher) diff --git a/src/robot/model/stats.py b/src/robot/model/stats.py index a4727302876..c41bca653cb 100644 --- a/src/robot/model/stats.py +++ b/src/robot/model/stats.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import string_types, text_type as unicode + from robot.utils import elapsed_time_to_string, html_escape, normalize from .tags import TagPatterns @@ -59,7 +61,7 @@ def _get_custom_attrs(self): return {} def _html_escape(self, item): - return html_escape(item) if isinstance(item, basestring) else item + return html_escape(item) if isinstance(item, string_types) else item @property def total(self): @@ -88,9 +90,12 @@ def __lt__(self, other): ## def __eq__(self, other): ## ... - def __nonzero__(self): + def __bool__(self): return not self.failed + def __nonzero__(self): + return self.__bool__() + def visit(self, visitor): visitor.visit_stat(self) diff --git a/src/robot/model/tags.py b/src/robot/model/tags.py index a11b94c9571..2fff0ec581f 100644 --- a/src/robot/model/tags.py +++ b/src/robot/model/tags.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3, string_types + import sys from robot.utils import Matcher, NormalizedDict, setter @@ -26,7 +28,7 @@ def __init__(self, tags=None): def _tags(self, tags): if not tags: return () - if isinstance(tags, basestring): + if isinstance(tags, string_types): tags = (tags,) return self._normalize(tags) @@ -63,13 +65,13 @@ def __repr__(self): return repr(list(self)) def __str__(self): - if sys.version_info[0] == 3: + if PY3: return self.__unicode__() return unicode(self).encode('UTF-8') - if sys.version_info[0] == 3: - def __bytes__(self): - return str(self).encode('UTF-8') + #PY3 + def __bytes__(self): + return str(self).encode('UTF-8') def __getitem__(self, index): item = self._tags[index] @@ -122,9 +124,9 @@ def match(self, tags): def __unicode__(self): return self._matcher.pattern - if sys.version_info[0] == 3: + if PY3: def __str__(self): - return self._matcher.pattern + return self.__unicode__() class _AndTagPattern(object): diff --git a/src/robot/model/tagsetter.py b/src/robot/model/tagsetter.py index df4a8cb7c9a..5a94eb12e44 100644 --- a/src/robot/model/tagsetter.py +++ b/src/robot/model/tagsetter.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from visitor import SuiteVisitor +from .visitor import SuiteVisitor class TagSetter(SuiteVisitor): @@ -31,5 +31,9 @@ def visit_test(self, test): def visit_keyword(self, keyword): pass - def __nonzero__(self): + def __bool__(self): return bool(self.add or self.remove) + + #PY2 + def __nonzero__(self): + return self.__bool__() diff --git a/src/robot/output/debugfile.py b/src/robot/output/debugfile.py index ee3ed8eb84f..fe604794fe3 100644 --- a/src/robot/output/debugfile.py +++ b/src/robot/output/debugfile.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3 + import sys from robot import utils @@ -19,8 +21,6 @@ from .logger import LOGGER from .loggerhelper import IsLogged -PY3 = sys.version_info[0] == 3 - def DebugFile(path): if not path: @@ -28,7 +28,7 @@ def DebugFile(path): return None try: outfile = open(path, 'w') - except EnvironmentError, err: + except EnvironmentError as err: LOGGER.error("Opening debug file '%s' failed: %s" % (path, err.strerror)) return None else: diff --git a/src/robot/output/filelogger.py b/src/robot/output/filelogger.py index 5e1a3ec9cd5..07b88503863 100644 --- a/src/robot/output/filelogger.py +++ b/src/robot/output/filelogger.py @@ -26,7 +26,7 @@ def __init__(self, path, level): def _get_writer(self, path): try: return open(path, 'w') - except EnvironmentError, err: + except EnvironmentError as err: raise DataError(err.strerror) def message(self, msg): diff --git a/src/robot/output/listeners.py b/src/robot/output/listeners.py index 45f56118bc8..99df75bedc9 100644 --- a/src/robot/output/listeners.py +++ b/src/robot/output/listeners.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import add_metaclass, text_type as unicode + import inspect import os.path @@ -51,8 +53,8 @@ def wrapped(self, *args): return wrapped +@add_metaclass(_RecursionAvoidingMetaclass) class Listeners(object): - __metaclass__ = _RecursionAvoidingMetaclass _start_attrs = ['doc', 'starttime', 'longname'] _end_attrs = _start_attrs + ['endtime', 'elapsedtime', 'status', 'message'] @@ -61,15 +63,19 @@ def __init__(self, listeners): self._running_test = False self._setup_or_teardown_type = None - def __nonzero__(self): + def __bool__(self): return bool(self._listeners) + #PY2 + def __nonzero__(self): + return self.__bool__() + def _import_listeners(self, listener_data): listeners = [] for name, args in listener_data: try: listeners.append(_ListenerProxy(name, args)) - except DataError, err: + except DataError as err: if args: name += ':' + ':'.join(args) LOGGER.error("Taking listener '%s' into use failed: %s" diff --git a/src/robot/output/logger.py b/src/robot/output/logger.py index 43486374d70..d85cfc40e09 100644 --- a/src/robot/output/logger.py +++ b/src/robot/output/logger.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import text_type as unicode + import os from robot.errors import DataError @@ -91,7 +93,7 @@ def register_file_logger(self, path=None, level='INFO'): return try: logger = FileLogger(path, level) - except DataError, err: + except DataError as err: self.error("Opening syslog file '%s' failed: %s" % (path, unicode(err))) else: self.register_logger(logger) diff --git a/src/robot/output/loggerhelper.py b/src/robot/output/loggerhelper.py index 8303460b627..b8518eee951 100644 --- a/src/robot/output/loggerhelper.py +++ b/src/robot/output/loggerhelper.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import text_type as unicode + from robot import utils from robot.errors import DataError from robot.model import Message as BaseMessage diff --git a/src/robot/output/stdoutlogsplitter.py b/src/robot/output/stdoutlogsplitter.py index 5c2e983e50b..abea1cc68ce 100644 --- a/src/robot/output/stdoutlogsplitter.py +++ b/src/robot/output/stdoutlogsplitter.py @@ -39,7 +39,7 @@ def _get_messages(self, output): def _split_output(self, output): tokens = self._split_from_levels.split(output) tokens = self._add_initial_level_and_time_if_needed(tokens) - for i in xrange(0, len(tokens), 3): + for i in range(0, len(tokens), 3): yield tokens[i:i+3] def _add_initial_level_and_time_if_needed(self, tokens): diff --git a/src/robot/output/xmllogger.py b/src/robot/output/xmllogger.py index 802187807a5..3f8239ccd8d 100644 --- a/src/robot/output/xmllogger.py +++ b/src/robot/output/xmllogger.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import text_type as unicode + from robot.errors import DataError from robot.utils import XmlWriter, NullMarkupWriter, get_timestamp, unic from robot.version import get_full_version @@ -33,7 +35,7 @@ def _get_writer(self, path, generator): return NullMarkupWriter() try: writer = XmlWriter(path, encoding='UTF-8') - except EnvironmentError, err: + except EnvironmentError as err: raise DataError("Opening output file '%s' failed: %s" % (path, err.strerror)) writer.start('robot', {'generator': get_full_version(generator), diff --git a/src/robot/parsing/comments.py b/src/robot/parsing/comments.py index d1231987bc7..65cd51ce1e1 100644 --- a/src/robot/parsing/comments.py +++ b/src/robot/parsing/comments.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import string_types + class CommentCache(object): @@ -22,7 +24,8 @@ def add(self, comment): self._comments.append(comment) def consume_with(self, function): - map(function, self._comments) + for comment in self._comments: + function(comment) self.__init__() @@ -43,7 +46,7 @@ def value(self): class Comment(object): def __init__(self, comment_data): - if isinstance(comment_data, basestring): + if isinstance(comment_data, string_types): comment_data = [comment_data] if comment_data else [] self._comment = comment_data or [] diff --git a/src/robot/parsing/datarow.py b/src/robot/parsing/datarow.py index a28960ec5ec..2033f1a57c8 100644 --- a/src/robot/parsing/datarow.py +++ b/src/robot/parsing/datarow.py @@ -103,5 +103,9 @@ def is_continuing(self): def is_commented(self): return bool(not self.cells and self.comments) - def __nonzero__(self): + def __bool__(self): return bool(self.cells or self.comments) + + #PY2 + def __nonzero__(self): + return self.__bool__() diff --git a/src/robot/parsing/htmlreader.py b/src/robot/parsing/htmlreader.py index 15e955078f3..d6c57ed572a 100644 --- a/src/robot/parsing/htmlreader.py +++ b/src/robot/parsing/htmlreader.py @@ -12,10 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3, text_type as unicode, unichr + import sys -from HTMLParser import HTMLParser -from htmlentitydefs import entitydefs +if PY3: + from html.parser import HTMLParser + from html.entities import entitydefs +else: + from HTMLParser import HTMLParser + from htmlentitydefs import entitydefs NON_BREAKING_SPACE = u'\xA0' diff --git a/src/robot/parsing/model.py b/src/robot/parsing/model.py index 7c794ea38d9..e4303071852 100644 --- a/src/robot/parsing/model.py +++ b/src/robot/parsing/model.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import string_types + import os import copy @@ -266,9 +268,13 @@ def directory(self): def report_invalid_syntax(self, message, level='ERROR'): self.parent.report_invalid_syntax(message, level) - def __nonzero__(self): + def __bool__(self): return bool(self._header or len(self)) + #PY2 + def __nonzero__(self): + return self.__bool__() + def __len__(self): return sum(1 for item in self) @@ -437,9 +443,13 @@ def __iter__(self): def is_started(self): return bool(self._header) - def __nonzero__(self): + def __bool__(self): return True + #PY2 + def __nonzero__(self): + return self.__bool__() + class KeywordTable(_Table): type = 'keyword' @@ -467,7 +477,7 @@ def __init__(self, parent, name, value, comment=None): self.name = name.rstrip('= ') if name.startswith('$') and value == []: value = '' - if isinstance(value, basestring): + if isinstance(value, string_types): value = [value] # Must support scalar lists until RF 2.8 (issue 939) self.value = value self.comment = Comment(comment) @@ -486,9 +496,13 @@ def is_for_loop(self): def has_data(self): return bool(self.name or ''.join(self.value)) - def __nonzero__(self): + def __bool__(self): return self.has_data() + #PY2 + def __nonzero__(self): + return self.__bool__() + def report_invalid_syntax(self, message, level='ERROR'): self.parent.report_invalid_syntax("Setting variable '%s' failed: %s" % (self.name, message), level) diff --git a/src/robot/parsing/populators.py b/src/robot/parsing/populators.py index 04ecb223eba..b8634f26fcf 100644 --- a/src/robot/parsing/populators.py +++ b/src/robot/parsing/populators.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import text_type as unicode + import os from robot.errors import DataError @@ -118,14 +120,14 @@ def _populate_init_file(self, datadir, init_file): datadir.initfile = init_file try: FromFilePopulator(datadir).populate(init_file) - except DataError, err: + except DataError as err: LOGGER.error(unicode(err)) def _populate_children(self, datadir, children, include_suites, warn_on_skipped): for child in children: try: datadir.add_child(child, include_suites) - except DataError, err: + except DataError as err: self._log_failed_parsing("Parsing data source '%s' failed: %s" % (child, unicode(err)), warn_on_skipped) diff --git a/src/robot/parsing/restreader.py b/src/robot/parsing/restreader.py index 2bc51b8b451..c5b87b71c52 100644 --- a/src/robot/parsing/restreader.py +++ b/src/robot/parsing/restreader.py @@ -12,10 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY2, PY3 + import sys -if sys.version_info[0] == 3: - from io import BytesIO -from cStringIO import StringIO +if PY3: + from io import BytesIO, StringIO +else: + from cStringIO import StringIO from .htmlreader import HtmlReader from .txtreader import TxtReader @@ -37,17 +40,13 @@ def read(self, rstfile, rawdata): return self._read_html(doctree, rawdata) def _read_text(self, data, rawdata): - if sys.version_info[0] == 3: - txtfile = StringIO(data) - else: - txtfile = StringIO(data.encode('UTF-8')) + if PY2: + data = data.encode('UTF-8') + txtfile = StringIO(data) return TxtReader().read(txtfile, rawdata) def _read_html(self, doctree, rawdata): - if sys.version_info[0] == 3: - htmlfile = BytesIO() - else: - htmlfile = StringIO() + htmlfile = BytesIO() if PY3 else StringIO() htmlfile.write(publish_from_doctree( doctree, writer_name='html', settings_overrides={'output_encoding': 'UTF-8'})) diff --git a/src/robot/parsing/settings.py b/src/robot/parsing/settings.py index 7b496595ab3..4775d15a93f 100644 --- a/src/robot/parsing/settings.py +++ b/src/robot/parsing/settings.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3, string_types + from .comments import Comment @@ -58,7 +60,7 @@ def report_invalid_syntax(self, message, level='ERROR'): self.parent.report_invalid_syntax(message, level) def _string_value(self, value): - return value if isinstance(value, basestring) else ' '.join(value) + return value if isinstance(value, string_types) else ' '.join(value) def _concat_string_with_value(self, string, value): if string: @@ -74,17 +76,23 @@ def _data_as_list(self): ret.extend(self.value) return ret - def __nonzero__(self): + def __bool__(self): return self.is_set() + #PY2 + def __nonzero__(self): + return self.__bool__() + def __iter__(self): return iter(self.value) def __unicode__(self): return unicode(self.value or '') - def __str__(self): - return self.__unicode__() + if PY3: + def __str__(self): + return str(self.value or '') + class StringValueJoiner(object): @@ -97,7 +105,7 @@ def join_string_with_value(self, string, value): return self.string_value(value) def string_value(self, value): - if isinstance(value, basestring): + if isinstance(value, string_types): return value return self._separator.join(value) @@ -111,7 +119,7 @@ def _populate(self, value): self.value = self._concat_string_with_value(self.value, value) def _string_value(self, value): - return value if isinstance(value, basestring) else ''.join(value) + return value if isinstance(value, string_types) else ''.join(value) def _data_as_list(self): return [self.setting_name, self.value] @@ -277,7 +285,7 @@ def __init__(self, parent, name, args=None, alias=None, comment=None): _Import.__init__(self, parent, name, args, alias, comment) def _split_alias(self, args): - if len(args) >= 2 and isinstance(args[-2], basestring) \ + if len(args) >= 2 and isinstance(args[-2], string_types) \ and args[-2].upper() == 'WITH NAME': return args[:-2], args[-1] return args, None diff --git a/src/robot/parsing/tablepopulators.py b/src/robot/parsing/tablepopulators.py index 541af097827..fcd99ece7a6 100644 --- a/src/robot/parsing/tablepopulators.py +++ b/src/robot/parsing/tablepopulators.py @@ -36,9 +36,13 @@ def add(self, row): def populate(self): pass - def __nonzero__(self): + def __bool__(self): return False + #PY2 + def __nonzero__(self): + return self.__bool__() + class _TablePopulator(Populator): @@ -89,9 +93,9 @@ def _get_populator(self, row): setter = self._table.get_setter(row.head) if not setter: return NullPopulator() - if setter.im_class is Documentation: + if setter.__self__.__class__ is Documentation: return DocumentationPopulator(setter) - if setter.im_class is MetadataList: + if setter.__self__.__class__ is MetadataList: return MetadataPopulator(setter) return SettingPopulator(setter) @@ -214,7 +218,7 @@ def _get_populator(self, row): setter = self._setting_setter(row) if not setter: return NullPopulator() - if setter.im_class is Documentation: + if setter.__self__.__class__ is Documentation: return DocumentationPopulator(setter) return SettingPopulator(setter) if row.starts_for_loop(): diff --git a/src/robot/reporting/jsbuildingcontext.py b/src/robot/reporting/jsbuildingcontext.py index 128a98f5e6f..5c694a6292a 100644 --- a/src/robot/reporting/jsbuildingcontext.py +++ b/src/robot/reporting/jsbuildingcontext.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import string_types, text_type as unicode + from contextlib import contextmanager import os.path @@ -27,7 +29,7 @@ class JsBuildingContext(object): def __init__(self, log_path=None, split_log=False, prune_input=False): # log_path can be a custom object in unit tests self._log_dir = os.path.dirname(log_path) \ - if isinstance(log_path, basestring) else None + if isinstance(log_path, string_types) else None self._split_log = split_log self._prune_input = prune_input self._strings = self._top_level_strings = StringCache() @@ -55,7 +57,7 @@ def timestamp(self, time): if not time: return None # Must use `long` due to http://ironpython.codeplex.com/workitem/31549 - millis = long(round(timestamp_to_secs(time) * 1000)) + millis = int(round(timestamp_to_secs(time) * 1000)) if self.basemillis is None: self.basemillis = millis return millis - self.basemillis diff --git a/src/robot/reporting/jsexecutionresult.py b/src/robot/reporting/jsexecutionresult.py index 4255373f13d..e612d4c5473 100644 --- a/src/robot/reporting/jsexecutionresult.py +++ b/src/robot/reporting/jsexecutionresult.py @@ -35,7 +35,7 @@ def _get_data(self, statistics, errors, basemillis): 'stats': statistics, 'errors': errors, 'baseMillis': basemillis, - 'generatedMillis': long(time.mktime(gentime) * 1000) - basemillis, + 'generatedMillis': int(time.mktime(gentime) * 1000) - basemillis, 'generatedTimestamp': utils.format_time(gentime, gmtsep=' ') } diff --git a/src/robot/reporting/jsmodelbuilders.py b/src/robot/reporting/jsmodelbuilders.py index b6c8d000be2..dbdad0cf524 100644 --- a/src/robot/reporting/jsmodelbuilders.py +++ b/src/robot/reporting/jsmodelbuilders.py @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement - from robot.output import LEVELS from .jsbuildingcontext import JsBuildingContext diff --git a/src/robot/reporting/jswriter.py b/src/robot/reporting/jswriter.py index 3774806a41e..6a849c13103 100644 --- a/src/robot/reporting/jswriter.py +++ b/src/robot/reporting/jswriter.py @@ -53,7 +53,7 @@ def _write_strings(self, strings): prefix = '%s = %s.concat(' % (variable, variable) postfix = ');\n' threshold = self._split_threshold - for index in xrange(0, len(strings), threshold): + for index in range(0, len(strings), threshold): self._write_json(prefix, strings[index:index+threshold], postfix) def _write_data(self, data): diff --git a/src/robot/reporting/logreportwriters.py b/src/robot/reporting/logreportwriters.py index 04844d94f4a..9059fe756ea 100644 --- a/src/robot/reporting/logreportwriters.py +++ b/src/robot/reporting/logreportwriters.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement +from six import string_types + from os.path import basename, splitext from robot.htmldata import HtmlFileWriter, ModelWriter, LOG, REPORT @@ -27,7 +28,7 @@ def __init__(self, js_model): def _write_file(self, path, config, template): outfile = open(path, 'w') \ - if isinstance(path, basestring) else path # unit test hook + if isinstance(path, string_types) else path # unit test hook with outfile: model_writer = RobotModelWriter(outfile, self._js_model, config) writer = HtmlFileWriter(outfile, model_writer) diff --git a/src/robot/reporting/resultwriter.py b/src/robot/reporting/resultwriter.py index 185f446587a..e2a5b269d47 100644 --- a/src/robot/reporting/resultwriter.py +++ b/src/robot/reporting/resultwriter.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import text_type as unicode + from robot.conf import RebotSettings from robot.errors import DataError from robot.output import LOGGER @@ -82,9 +84,9 @@ def _write_report(self, js_result, path, config): def _write(self, name, writer, path, *args): try: writer(path, *args) - except DataError, err: + except DataError as err: LOGGER.error(unicode(err)) - except EnvironmentError, err: + except EnvironmentError as err: # `err.filename` can be different than `path` at least if reading # log/report templates or writing split log fails. # `unic` is needed due to http://bugs.jython.org/issue1825. diff --git a/src/robot/reporting/stringcache.py b/src/robot/reporting/stringcache.py index 20883a293de..26408abd542 100644 --- a/src/robot/reporting/stringcache.py +++ b/src/robot/reporting/stringcache.py @@ -12,25 +12,30 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3 + import sys from operator import itemgetter from robot.utils import compress_text -# Normally handled by 2to3, but not for long as base type: -if sys.version_info[0] == 3: +if PY3: long = int - +#TODO: Still needed? class StringIndex(long): # Methods below are needed due to http://bugs.jython.org/issue1828 def __str__(self): return long.__str__(self).rstrip('L') - def __nonzero__(self): + def __bool__(self): return bool(long(self)) + #PY2 + def __nonzero__(self): + return self.__bool__() + class StringCache(object): _compress_threshold = 80 diff --git a/src/robot/result/configurer.py b/src/robot/result/configurer.py index 1460e610403..d2ce13d4a1f 100644 --- a/src/robot/result/configurer.py +++ b/src/robot/result/configurer.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import string_types + from robot import utils from robot import model @@ -44,7 +46,7 @@ def __init__(self, remove_keywords=None, log_level=None, start_time=None, def _get_remove_keywords(self, value): if value is None: return [] - if isinstance(value, basestring): + if isinstance(value, string_types): return [value] return value diff --git a/src/robot/result/executionresult.py b/src/robot/result/executionresult.py index 40d01f6224d..89a48a17f52 100644 --- a/src/robot/result/executionresult.py +++ b/src/robot/result/executionresult.py @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement - from robot.model import Statistics from .executionerrors import ExecutionErrors diff --git a/src/robot/result/flattenkeywordmatcher.py b/src/robot/result/flattenkeywordmatcher.py index fe1cee1a857..1c671ee4298 100644 --- a/src/robot/result/flattenkeywordmatcher.py +++ b/src/robot/result/flattenkeywordmatcher.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import string_types + from robot.errors import DataError from robot.utils import MultiMatcher @@ -22,7 +24,7 @@ def __init__(self, flattened): self.match = MultiMatcher(self._yield_patterns(flattened)).match def _yield_patterns(self, flattened): - if isinstance(flattened, basestring): + if isinstance(flattened, string_types): flattened = [flattened] for flat in flattened: if not flat.upper().startswith('NAME:'): diff --git a/src/robot/result/resultbuilder.py b/src/robot/result/resultbuilder.py index 39b9a093495..787b77363ab 100644 --- a/src/robot/result/resultbuilder.py +++ b/src/robot/result/resultbuilder.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement +from six import text_type as unicode from robot.errors import DataError from robot.utils import ET, ETSource, get_error_message @@ -61,7 +61,7 @@ def _single_result(source, options): ets = ETSource(source) try: return ExecutionResultBuilder(ets, **options).build(Result(source)) - except IOError, err: + except IOError as err: error = err.strerror except: error = get_error_message() diff --git a/src/robot/result/testcase.py b/src/robot/result/testcase.py index a6428bd1eea..9c9b990032e 100644 --- a/src/robot/result/testcase.py +++ b/src/robot/result/testcase.py @@ -14,7 +14,7 @@ from robot import model, utils -from keyword import Keyword +from .keyword import Keyword class TestCase(model.TestCase): diff --git a/src/robot/run.py b/src/robot/run.py index c8c687ed636..253db61d224 100755 --- a/src/robot/run.py +++ b/src/robot/run.py @@ -29,6 +29,7 @@ This module also provides :func:`run` and :func:`run_cli` functions that can be used programmatically. Other code is for internal usage. """ +from six import text_type as unicode USAGE = """Robot Framework -- A generic test automation framework diff --git a/src/robot/running/arguments/argumentresolver.py b/src/robot/running/arguments/argumentresolver.py index 378ef1ba999..8df78500670 100644 --- a/src/robot/running/arguments/argumentresolver.py +++ b/src/robot/running/arguments/argumentresolver.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import string_types + from robot.errors import DataError from robot.utils import is_dict_like @@ -56,7 +58,7 @@ def resolve(self, arguments): return positional, named def _is_named(self, arg): - if not isinstance(arg, basestring) or '=' not in arg: + if not isinstance(arg, string_types) or '=' not in arg: return False name = arg.split('=')[0] if self._is_escaped(name): diff --git a/src/robot/running/arguments/argumentspec.py b/src/robot/running/arguments/argumentspec.py index ba3294f0faa..37937374c37 100644 --- a/src/robot/running/arguments/argumentspec.py +++ b/src/robot/running/arguments/argumentspec.py @@ -33,4 +33,4 @@ def minargs(self): @property def maxargs(self): - return len(self.positional) if not self.varargs else sys.maxint + return len(self.positional) if not self.varargs else sys.maxsize diff --git a/src/robot/running/arguments/javaargumentcoercer.py b/src/robot/running/arguments/javaargumentcoercer.py index d7deba536d2..5ea5e740592 100644 --- a/src/robot/running/arguments/javaargumentcoercer.py +++ b/src/robot/running/arguments/javaargumentcoercer.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import string_types + from java.lang import Byte, Short, Integer, Long, Boolean, Float, Double from robot.variables import contains_var @@ -76,7 +78,7 @@ def handles(self, type): return type in self._types or type.__name__ in self._primitives def coerce(self, argument, dryrun=False): - if not isinstance(argument, basestring) \ + if not isinstance(argument, string_types) \ or (dryrun and contains_var(argument)): return argument try: diff --git a/src/robot/running/builder.py b/src/robot/running/builder.py index 4f9fdb2f1c0..029b1f93d2e 100644 --- a/src/robot/running/builder.py +++ b/src/robot/running/builder.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import text_type as unicode + from robot.errors import DataError from robot.parsing import TestData from robot.running.defaults import TestDefaults @@ -60,7 +62,7 @@ def _parse(self, path): return TestData(source=abspath(path), include_suites=self.include_suites, warn_on_skipped=self.warn_on_skipped) - except DataError, err: + except DataError as err: raise DataError("Parsing '%s' failed: %s" % (path, unicode(err))) def _build_suite(self, data, parent_defaults=None): diff --git a/src/robot/running/context.py b/src/robot/running/context.py index 3a9bb6a95ed..df7360d7018 100644 --- a/src/robot/running/context.py +++ b/src/robot/running/context.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement +from six import text_type as unicode from contextlib import contextmanager diff --git a/src/robot/running/dynamicmethods.py b/src/robot/running/dynamicmethods.py index feef126b82e..dc129d941d5 100644 --- a/src/robot/running/dynamicmethods.py +++ b/src/robot/running/dynamicmethods.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import string_types, text_type as unicode + from robot.errors import DataError from robot.utils import get_error_message, unic, is_java_method @@ -55,7 +57,7 @@ def _handle_return_value(self, value): raise NotImplementedError def _to_string(self, value): - if not isinstance(value, basestring): + if not isinstance(value, string_types): raise DataError('Return value must be string.') return value if isinstance(value, unicode) else unic(value, 'UTF-8') @@ -65,9 +67,13 @@ def _to_list_of_strings(self, value): except (TypeError, DataError): raise DataError('Return value must be list of strings.') - def __nonzero__(self): + def __bool__(self): return self.method is not no_dynamic_method + #PY2 + def __nonzero__(self): + return self.__bool__() + class GetKeywordNames(_DynamicMethod): _underscore_name = 'get_keyword_names' @@ -90,7 +96,7 @@ def _supports_python_kwargs(self, method): return len(spec.positional) == 3 def _supports_java_kwargs(self, method): - func = self.method.im_func if hasattr(method, 'im_func') else method + func = self.method.__func__ if hasattr(method, 'im_func') else method signatures = func.argslist[:func.nargs] spec = JavaArgumentParser().parse(signatures) return (self._java_single_signature_kwargs(spec) or diff --git a/src/robot/running/handlers.py b/src/robot/running/handlers.py index 2cc5250216f..f56c229d099 100644 --- a/src/robot/running/handlers.py +++ b/src/robot/running/handlers.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement +from six import string_types from robot import utils from robot.errors import DataError diff --git a/src/robot/running/importer.py b/src/robot/running/importer.py index b83fd643416..61dd95cc3b4 100644 --- a/src/robot/running/importer.py +++ b/src/robot/running/importer.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import string_types + import os.path import copy @@ -95,7 +97,7 @@ def __init__(self): self._items = [] def __setitem__(self, key, item): - if not isinstance(key, (basestring, tuple)): + if not isinstance(key, (string_types) + (tuple,)): raise FrameworkError('Invalid key for ImportCache') key = self._norm_path_key(key) if key not in self._keys: @@ -127,4 +129,4 @@ def _norm_path_key(self, key): return key def _is_path(self, key): - return isinstance(key, basestring) and os.path.isabs(key) and os.path.exists(key) + return isinstance(key, string_types) and os.path.isabs(key) and os.path.exists(key) diff --git a/src/robot/running/keywords.py b/src/robot/running/keywords.py index 4463d030c2b..40374933679 100644 --- a/src/robot/running/keywords.py +++ b/src/robot/running/keywords.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import text_type as unicode + from robot.utils import (format_assign_message, get_elapsed_time, get_error_message, get_timestamp, plural_or_not) from robot.errors import (ContinueForLoop, DataError, ExecutionFailed, @@ -46,10 +48,10 @@ def run(self, context): for kw in self._keywords: try: kw.run(context) - except ExecutionPassed, exception: + except ExecutionPassed as exception: exception.set_earlier_failures(errors) raise exception - except ExecutionFailed, exception: + except ExecutionFailed as exception: errors.extend(exception.get_errors()) if not exception.can_continue(context.in_teardown, self._templated, @@ -58,9 +60,13 @@ def run(self, context): if errors: raise ExecutionFailures(errors) - def __nonzero__(self): + def __bool__(self): return bool(self._keywords) + #PY2 + def __nonzero__(self): + return self.__bool__() + def __iter__(self): return iter(self._keywords) @@ -103,7 +109,7 @@ def run(self, context): handler = self._start(context) try: return_value = self._run(handler, context) - except ExecutionFailed, err: + except ExecutionFailed as err: self.status = self._get_status(err) self._end(context, error=err) raise @@ -157,7 +163,7 @@ def _set_variables(self, context, return_value, error): return_value = error.return_value try: VariableAssigner(self.assign).assign(context, return_value) - except DataError, err: + except DataError as err: self.status = 'FAIL' msg = unicode(err) context.output.fail(msg) @@ -200,9 +206,9 @@ def run(self, context): def _run_with_error_handling(self, runnable, context): try: runnable(context) - except ExecutionFailed, err: + except ExecutionFailed as err: return err - except DataError, err: + except DataError as err: msg = unicode(err) context.output.fail(msg) return ExecutionFailed(msg, syntax=True) @@ -254,7 +260,7 @@ def _get_items_and_iteration_steps(self, context): if context.dry_run: return self.vars, [0] items = self._replace_vars_from_items(context.variables) - return items, range(0, len(items), len(self.vars)) + return items, list(range(0, len(items), len(self.vars))) def _run_one_round(self, context, variables, values): foritem = _ForItem(variables, values) @@ -285,7 +291,7 @@ def _get_range_items(self, items): if not 1 <= len(items) <= 3: raise DataError('FOR IN RANGE expected 1-3 arguments, ' 'got %d instead.' % len(items)) - return range(*items) + return list(range(*items)) def _to_int_with_arithmetics(self, item): item = str(item) diff --git a/src/robot/running/model.py b/src/robot/running/model.py index 809ffacba48..e2986108afa 100644 --- a/src/robot/running/model.py +++ b/src/robot/running/model.py @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement - from robot import model from robot.conf import RobotSettings from robot.output import LOGGER, Output, pyloggingconf diff --git a/src/robot/running/namespace.py b/src/robot/running/namespace.py index 90ed8dbe854..0c84323ad0e 100644 --- a/src/robot/running/namespace.py +++ b/src/robot/running/namespace.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import string_types, text_type as unicode + import os import copy from itertools import chain @@ -75,7 +77,7 @@ def _handle_imports(self, import_settings): if not item.name: raise DataError('%s setting requires a name' % item.type) self._import(item) - except DataError, err: + except DataError as err: item.report_invalid_syntax(unicode(err)) def _import(self, import_setting): @@ -144,7 +146,7 @@ def _resolve_name(self, import_setting): name = import_setting.name try: name = self.variables.replace_string(name) - except DataError, err: + except DataError as err: self._raise_replacing_vars_failed(import_setting, err) return self._get_path(name, import_setting.directory, import_setting.type) @@ -163,7 +165,7 @@ def _is_library_by_path(self, path): def _resolve_args(self, import_setting): try: return self.variables.replace_list(import_setting.args) - except DataError, err: + except DataError as err: self._raise_replacing_vars_failed(import_setting, err) def _import_deprecated_standard_libs(self, name): @@ -208,7 +210,7 @@ def get_handler(self, name): handler = self._get_handler(name) if handler is None: raise DataError("No keyword with name '%s' found." % name) - except DataError, err: + except DataError as err: handler = UserErrorHandler(name, unicode(err)) self._replace_variables_from_user_handlers(handler) return handler @@ -221,7 +223,7 @@ def _get_handler(self, name): handler = None if not name: raise DataError('Keyword name cannot be empty.') - if not isinstance(name, basestring): + if not isinstance(name, string_types): raise DataError('Keyword name must be a string.') if '.' in name: handler = self._get_explicit_handler(name) @@ -458,7 +460,7 @@ def keys(self): return self.current.keys() def has_key(self, key): - return self.current.has_key(key) + return key in self.current __contains__ = has_key diff --git a/src/robot/running/outputcapture.py b/src/robot/running/outputcapture.py index 95b39ac7889..0f2f118a458 100644 --- a/src/robot/running/outputcapture.py +++ b/src/robot/running/outputcapture.py @@ -12,8 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3 + import sys -from StringIO import StringIO +if PY3: + from io import StringIO +else: + from StringIO import StringIO from robot.output import LOGGER from robot.utils import decode_output, encode_output diff --git a/src/robot/running/runkwregister.py b/src/robot/running/runkwregister.py index 6129270c70a..78e6deb5b83 100644 --- a/src/robot/running/runkwregister.py +++ b/src/robot/running/runkwregister.py @@ -42,9 +42,9 @@ def _get_args_from_method(self, method): # Python 3 has no unbound methods, they are just functions, # so ismethod won't be True... if inspect.ismethod(method): - return method.im_func.func_code.co_argcount - 1 + return method.__func__.__code__.co_argcount - 1 elif inspect.isfunction(method): - code = method.func_code + code = method.__code__ argcount = code.co_argcount # ...but you can look at the args: #TODO: Better solution? diff --git a/src/robot/running/runner.py b/src/robot/running/runner.py index 26658c70383..2f93c84d75a 100644 --- a/src/robot/running/runner.py +++ b/src/robot/running/runner.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement +from six import text_type as unicode from robot.errors import ExecutionFailed, DataError, PassExecution from robot.model import SuiteVisitor @@ -118,7 +118,7 @@ def visit_test(self, test): status.test_failed('Test case contains no keywords.', result.critical) try: result.tags = self._context.variables.replace_list(result.tags) - except DataError, err: + except DataError as err: status.test_failed('Replacing variables from test tags failed: %s' % unicode(err), result.critical) self._context.start_test(result) @@ -127,13 +127,13 @@ def visit_test(self, test): try: if not status.failures: keywords.run(self._context) - except PassExecution, exception: + except PassExecution as exception: err = exception.earlier_failures if err: status.test_failed(err, result.critical) else: result.message = exception.message - except ExecutionFailed, err: + except ExecutionFailed as err: status.test_failed(err, result.critical) if err.timeout: self._context.timeout_occurred = True @@ -180,14 +180,14 @@ def _run_setup_or_teardown(self, data, kw_type): return None try: name = self._variables.replace_string(data.name) - except DataError, err: + except DataError as err: return err if name.upper() in ('', 'NONE'): return None kw = Keyword(name, data.args, type=kw_type) try: kw.run(self._context) - except ExecutionFailed, err: + except ExecutionFailed as err: if err.timeout: self._context.timeout_occurred = True return err diff --git a/src/robot/running/signalhandler.py b/src/robot/running/signalhandler.py index 735e27cdf24..f2efa83afc6 100644 --- a/src/robot/running/signalhandler.py +++ b/src/robot/running/signalhandler.py @@ -73,7 +73,7 @@ def __exit__(self, *exc_info): def _register_signal_handler(self, signum): try: signal.signal(signum, self) - except (ValueError, IllegalArgumentException), err: + except (ValueError, IllegalArgumentException) as err: # ValueError occurs e.g. if Robot doesn't run on main thread. # IllegalArgumentException is http://bugs.jython.org/issue1729 if currentThread().getName() == 'MainThread': diff --git a/src/robot/running/status.py b/src/robot/running/status.py index 7f7c1c08556..3afd3b58b09 100644 --- a/src/robot/running/status.py +++ b/src/robot/running/status.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import text_type as unicode + from robot.errors import PassExecution diff --git a/src/robot/running/testlibraries.py b/src/robot/running/testlibraries.py index 9f39685e03a..5db0a1e4414 100644 --- a/src/robot/running/testlibraries.py +++ b/src/robot/running/testlibraries.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement import inspect import os import sys diff --git a/src/robot/running/timeouts/__init__.py b/src/robot/running/timeouts/__init__.py index 020e2aaa2b1..68a3eb1851e 100644 --- a/src/robot/running/timeouts/__init__.py +++ b/src/robot/running/timeouts/__init__.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3, text_type as unicode + import sys import os import time @@ -55,7 +57,7 @@ def replace_variables(self, variables): self.secs = utils.timestr_to_secs(self.string) self.string = utils.secs_to_timestr(self.secs) self.message = variables.replace_string(self.message) - except (DataError, ValueError), err: + except (DataError, ValueError) as err: self.secs = 0.000001 # to make timeout active self.error = 'Setting %s timeout failed: %s' \ % (self.type.lower(), unicode(err)) @@ -76,7 +78,7 @@ def timed_out(self): return self.active and self.time_left() <= 0 def __str__(self): - if sys.version_info[0] == 3: + if PY3: return self.__unicode__() return unicode(self).encode('utf-8') @@ -96,9 +98,13 @@ def __lt__(self, other): ## def __eq__(self, other): ## ... - def __nonzero__(self): + def __bool__(self): return bool(self.string and self.string.upper() != 'NONE') + #PY2 + def __nonzero__(self): + return self.__bool__() + def run(self, runnable, args=None, kwargs=None): if self.error: raise DataError(self.error) diff --git a/src/robot/running/timeouts/timeoutthread.py b/src/robot/running/timeouts/timeoutthread.py index 14536a9d791..192322a442e 100644 --- a/src/robot/running/timeouts/timeoutthread.py +++ b/src/robot/running/timeouts/timeoutthread.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import reraise + import sys from threading import Event @@ -55,7 +57,7 @@ def run_in_thread(self, timeout): def get_result(self): if self._error: - raise self._error, None, self._traceback + reraise(self._error, None, self._traceback) return self._result def stop_thread(self): diff --git a/src/robot/running/timeouts/timeoutwin.py b/src/robot/running/timeouts/timeoutwin.py index ea685945012..f30e024b90b 100644 --- a/src/robot/running/timeouts/timeoutwin.py +++ b/src/robot/running/timeouts/timeoutwin.py @@ -12,8 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3 + import ctypes -import thread +if PY3: + import _thread as thread +else: + import thread import time from threading import Timer diff --git a/src/robot/running/userkeyword.py b/src/robot/running/userkeyword.py index 5e014cd4d8e..97e35bd7401 100644 --- a/src/robot/running/userkeyword.py +++ b/src/robot/running/userkeyword.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement +from six import text_type as unicode import os import re @@ -41,7 +41,7 @@ def __init__(self, user_keywords, path=None): for kw in user_keywords: try: handler = self._create_handler(kw) - except DataError, err: + except DataError as err: LOGGER.error("Creating user keyword '%s' failed: %s" % (kw.name, unicode(err))) continue @@ -79,7 +79,7 @@ def has_handler(self, name): def get_handler(self, name): try: return BaseLibrary.get_handler(self, name) - except DataError, error: + except DataError as error: found = self._get_embedded_arg_handlers(name) if not found: raise error @@ -179,13 +179,13 @@ def _execute(self, context, arguments): error = return_ = pass_ = None try: self.keywords.run(context) - except ReturnFromKeyword, exception: + except ReturnFromKeyword as exception: return_ = exception error = exception.earlier_failures - except ExecutionPassed, exception: + except ExecutionPassed as exception: pass_ = exception error = exception.earlier_failures - except ExecutionFailed, exception: + except ExecutionFailed as exception: error = exception with context.keyword_teardown(error): td_error = self._run_teardown(context) @@ -219,7 +219,7 @@ def _run_teardown(self, context): return None try: name = context.variables.replace_string(self.teardown.name) - except DataError, err: + except DataError as err: return ExecutionFailed(unicode(err), syntax=True) if name.upper() in ('', 'NONE'): return None @@ -228,7 +228,7 @@ def _run_teardown(self, context): kw.run(context) except PassExecution: return None - except ExecutionFailed, err: + except ExecutionFailed as err: return err return None @@ -244,7 +244,7 @@ def _get_return_value(self, variables, return_): contains_list_var = any(is_list_var(item) for item in ret) try: ret = variables.replace_list(ret) - except DataError, err: + except DataError as err: raise DataError('Replacing variables from keyword return value ' 'failed: %s' % unicode(err)) if len(ret) != 1 or contains_list_var: @@ -323,7 +323,7 @@ def __init__(self, name, template): if not match: raise TypeError('Does not match given name') UserKeywordHandler.__init__(self, template.keyword, template.libname) - self.embedded_args = zip(template.embedded_args, match.groups()) + self.embedded_args = list(zip(template.embedded_args, match.groups())) self.name = name self.orig_name = template.name diff --git a/src/robot/testdoc.py b/src/robot/testdoc.py index e006b022831..77547f67f09 100755 --- a/src/robot/testdoc.py +++ b/src/robot/testdoc.py @@ -27,8 +27,7 @@ This module also provides :func:`testdoc` and :func:`testdoc_cli` functions that can be used programmatically. Other code is for internal usage. """ - -from __future__ import with_statement +from six import string_types USAGE = """robot.testdoc -- Robot Framework test data documentation tool @@ -116,7 +115,7 @@ def _write_test_doc(self, suite, outfile, title): @disable_curdir_processing def TestSuiteFactory(datasources, **options): settings = RobotSettings(options) - if isinstance(datasources, basestring): + if isinstance(datasources, string_types): datasources = [datasources] suite = TestSuiteBuilder().build(*datasources) suite.configure(**settings.suite_config) @@ -142,7 +141,7 @@ def write_data(self): 'suite': JsonConverter(self._output_path).convert(self._suite), 'title': self._title, 'generated': utils.format_time(generated_time, gmtsep=' '), - 'generatedMillis': long(time.mktime(generated_time) * 1000) + 'generatedMillis': int(time.mktime(generated_time) * 1000) } JsonWriter(self._output).write_json('testdoc = ', model) diff --git a/src/robot/tidy.py b/src/robot/tidy.py index 3a276234e59..71326aeaa4a 100755 --- a/src/robot/tidy.py +++ b/src/robot/tidy.py @@ -27,6 +27,7 @@ This module also provides :class:`Tidy` class and :func:`tidy_cli` function that can be used programmatically. Other code is for internal usage. """ +from six import PY3, string_types, text_type as unicode USAGE = """robot.tidy -- Robot Framework test data clean-up tool @@ -110,7 +111,10 @@ import os import sys -from StringIO import StringIO +if PY3: + from io import StringIO +else: + from StringIO import StringIO # Allows running as a script. __name__ check needed with multiprocessing: # http://code.google.com/p/robotframework/issues/detail?id=1137 @@ -149,7 +153,7 @@ def file(self, path, output=None): Use :func:`inplace` to tidy files in-place. """ data = self._parse_data(path) - mode = 'w' if sys.version_info[0] == 3 else 'wb' + mode = 'w' if PY3 else 'wb' outfile = open(output, mode) if output else StringIO() try: self._save_file(data, outfile) diff --git a/src/robot/utils/application.py b/src/robot/utils/application.py index eb3789dad80..c08ad914f2c 100644 --- a/src/robot/utils/application.py +++ b/src/robot/utils/application.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement +from six import text_type as unicode + import sys from contextlib import contextmanager @@ -46,7 +47,7 @@ def execute_cli(self, cli_arguments): def console(self, msg): if msg: - print encode_output(msg) + print(encode_output(msg)) @contextmanager def _logging(self): @@ -60,9 +61,9 @@ def _logging(self): def _parse_arguments(self, cli_args): try: options, arguments = self.parse_arguments(cli_args) - except Information, msg: + except Information as msg: self._report_info(unicode(msg)) - except DataError, err: + except DataError as err: self._report_error(unicode(err), help=True, exit=True) else: self._logger.info('Arguments: %s' % ','.join(arguments)) @@ -85,7 +86,7 @@ def execute(self, *arguments, **options): def _execute(self, arguments, options): try: rc = self.main(arguments, **options) - except DataError, err: + except DataError as err: return self._report_error(unicode(err), help=True) except (KeyboardInterrupt, SystemExit): return self._report_error('Execution stopped by user.', @@ -125,7 +126,7 @@ def info(self, message): pass def error(self, message): - print encode_output(message) + print(encode_output(message)) def close(self): pass diff --git a/src/robot/utils/argumentparser.py b/src/robot/utils/argumentparser.py index 5bdbfc5a773..52fa2c8f599 100644 --- a/src/robot/utils/argumentparser.py +++ b/src/robot/utils/argumentparser.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement +from six import string_types + import getopt # optparse was not supported by Jython 2.2 import os import re @@ -157,7 +158,7 @@ def _parse_args(self, args): args = [self._lowercase_long_option(a) for a in args] try: opts, args = getopt.getopt(args, self._short_opts, self._long_opts) - except getopt.GetoptError, err: + except getopt.GetoptError as err: raise DataError(err.msg) return self._process_opts(opts), self._glob_args(args) @@ -280,7 +281,7 @@ def _create_option(self, short_opts, long_opt, takes_arg, is_multi): self._short_opts += (''.join(short_opts)) def _get_pythonpath(self, paths): - if isinstance(paths, basestring): + if isinstance(paths, string_types): paths = [paths] temp = [] for path in self._split_pythonpath(paths): @@ -342,11 +343,11 @@ def __init__(self, arg_limits): def _parse_arg_limits(self, arg_limits): if arg_limits is None: - return 0, sys.maxint + return 0, sys.maxsize if isinstance(arg_limits, int): return arg_limits, arg_limits if len(arg_limits) == 1: - return arg_limits[0], sys.maxint + return arg_limits[0], sys.maxsize return arg_limits[0], arg_limits[1] def __call__(self, args): @@ -357,7 +358,7 @@ def _raise_invalid_args(self, min_args, max_args, arg_count): min_end = plural_or_not(min_args) if min_args == max_args: expectation = "%d argument%s" % (min_args, min_end) - elif max_args != sys.maxint: + elif max_args != sys.maxsize: expectation = "%d to %d arguments" % (min_args, max_args) else: expectation = "at least %d argument%s" % (min_args, min_end) @@ -400,7 +401,7 @@ def _read_from_file(self, path): try: with Utf8Reader(path) as reader: return reader.read() - except (IOError, UnicodeError), err: + except (IOError, UnicodeError) as err: raise DataError("Opening argument file '%s' failed: %s" % (path, err)) diff --git a/src/robot/utils/asserts.py b/src/robot/utils/asserts.py index ea08cdb34b8..82ff1ecd342 100644 --- a/src/robot/utils/asserts.py +++ b/src/robot/utils/asserts.py @@ -99,6 +99,8 @@ def test_new_style(self): FAILED (failures=2) """ +from six import PY3, text_type as unicode + from .unic import unic @@ -151,7 +153,7 @@ def fail_unless_raises(exc_class, callable_obj, *args, **kwargs): """ try: callable_obj(*args, **kwargs) - except exc_class, err: + except exc_class as err: return err else: if hasattr(exc_class,'__name__'): @@ -165,7 +167,7 @@ def fail_unless_raises_with_msg(exc_class, expected_msg, callable_obj, *args, """Similar to fail_unless_raises but also checks the exception message.""" try: callable_obj(*args, **kwargs) - except exc_class, err: + except exc_class as err: assert_equal(expected_msg, unic(err), 'Correct exception but wrong message') else: if hasattr(exc_class,'__name__'): @@ -251,6 +253,9 @@ def _get_default_message(obj1, obj2, delim): str2, _type_name(obj2)) return '%s %s %s' % (str1, delim, str2) +if PY3: + long = int + def _type_name(val): known_types = {int: 'number', long: 'number', float: 'number', str: 'string', unicode: 'string', bool: 'boolean'} diff --git a/src/robot/utils/connectioncache.py b/src/robot/utils/connectioncache.py index 4f6d81206d4..18c74b5cfff 100644 --- a/src/robot/utils/connectioncache.py +++ b/src/robot/utils/connectioncache.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import string_types + from .normalizing import NormalizedDict @@ -61,7 +63,7 @@ def register(self, connection, alias=None): self.current = connection self._connections.append(connection) index = len(self._connections) - if isinstance(alias, basestring): + if isinstance(alias, string_types): self._aliases[alias] = index return index @@ -129,9 +131,13 @@ def __iter__(self): def __len__(self): return len(self._connections) - def __nonzero__(self): + def __bool__(self): return self.current is not self._no_current + #PY2 + def __nonzero__(self): + return self.__bool__() + def _resolve_alias_or_index(self, alias_or_index): try: return self._resolve_alias(alias_or_index) @@ -139,7 +145,7 @@ def _resolve_alias_or_index(self, alias_or_index): return self._resolve_index(alias_or_index) def _resolve_alias(self, alias): - if isinstance(alias, basestring): + if isinstance(alias, string_types): try: return self._aliases[alias] except KeyError: @@ -169,5 +175,9 @@ def __getattr__(self, name): def raise_error(self): raise RuntimeError(self.message) - def __nonzero__(self): + def __bool__(self): return False + + #PY2 + def __nonzero__(self): + return self.__bool__() diff --git a/src/robot/utils/encoding.py b/src/robot/utils/encoding.py index 75d596de878..7da9807e07a 100644 --- a/src/robot/utils/encoding.py +++ b/src/robot/utils/encoding.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3, text_type as unicode + import sys from .encodingsniffer import get_output_encoding, get_system_encoding @@ -37,7 +39,7 @@ def decode_output(string, force=False): def encode_output(string, errors='replace'): """Encodes Unicode to bytes in console encoding.""" # http://ironpython.codeplex.com/workitem/29487 - if sys.version_info[0] == 3 or sys.platform == 'cli': + if PY3 or sys.platform == 'cli': return string return string.encode(OUTPUT_ENCODING, errors) @@ -55,6 +57,6 @@ def decode_from_system(string, can_be_from_java=True): def encode_to_system(string, errors='replace'): """Encodes Unicode to system encoding (e.g. cli args and env vars).""" - if sys.version_info[0] == 3: + if PY3: return string return string.encode(SYSTEM_ENCODING, errors) diff --git a/src/robot/utils/error.py b/src/robot/utils/error.py index fb4b7765757..c559d5e4f94 100644 --- a/src/robot/utils/error.py +++ b/src/robot/utils/error.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import text_type as unicode + import re import sys import traceback diff --git a/src/robot/utils/escaping.py b/src/robot/utils/escaping.py index 7293a0b2c0d..e433f610568 100644 --- a/src/robot/utils/escaping.py +++ b/src/robot/utils/escaping.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import string_types, unichr + import re @@ -19,7 +21,7 @@ def escape(item): - if not isinstance(item, basestring): + if not isinstance(item, string_types): return item for seq in _SEQS_TO_BE_ESCAPED: if seq in item: @@ -28,7 +30,7 @@ def escape(item): def unescape(item): - if not (isinstance(item, basestring) and '\\' in item): + if not (isinstance(item, string_types) and '\\' in item): return item return Unescaper().unescape(item) diff --git a/src/robot/utils/etreewrapper.py b/src/robot/utils/etreewrapper.py index b0f267d0874..0e653228521 100644 --- a/src/robot/utils/etreewrapper.py +++ b/src/robot/utils/etreewrapper.py @@ -12,14 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3, string_types + import sys import os.path -PY3 = sys.version_info[0] == 3 - if PY3: - from io import BytesIO -from StringIO import StringIO + from io import BytesIO, StringIO +else: + from StringIO import StringIO _IRONPYTHON = sys.platform == 'cli' @@ -78,13 +79,13 @@ def __str__(self): return '' def _source_is_file_name(self): - return isinstance(self._source, basestring) \ + return isinstance(self._source, string_types) \ and not self._source.lstrip().startswith('<') def _open_source_if_necessary(self): if self._source_is_file_name(): return self._open_file(self._source) - if isinstance(self._source, basestring): + if isinstance(self._source, string_types): return self._open_string_io(self._source) if PY3 and isinstance(self._source, bytes): return self._open_bytes_io(self._source) diff --git a/src/robot/utils/importer.py b/src/robot/utils/importer.py index ea47beb4e1b..d7fcea37358 100644 --- a/src/robot/utils/importer.py +++ b/src/robot/utils/importer.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement +from six import reraise, text_type as unicode + import os import sys import inspect @@ -60,7 +61,7 @@ def import_class_or_module(self, name, instantiate_with_args=None): imported, source = self._import_class_or_module(name) self._log_import_succeeded(imported, name, source) return self._instantiate_if_needed(imported, instantiate_with_args) - except DataError, err: + except DataError as err: self._raise_import_failed(name, err) def _import_class_or_module(self, name): @@ -83,7 +84,7 @@ def import_class_or_module_by_path(self, path, instantiate_with_args=None): imported, source = self._by_path_importer.import_(path) self._log_import_succeeded(imported, imported.__name__, source) return self._instantiate_if_needed(imported, instantiate_with_args) - except DataError, err: + except DataError as err: self._raise_import_failed(path, err) def _raise_import_failed(self, name, error): @@ -146,7 +147,7 @@ def _import(self, name, fromlist=None, retry=True): return self._import(name, fromlist, retry=False) # Cannot use plain raise due to # http://ironpython.codeplex.com/workitem/32332 - raise sys.exc_type, sys.exc_value, sys.exc_traceback + reraise(*sys.exc_info()) except: raise DataError(*get_error_details()) diff --git a/src/robot/utils/islike.py b/src/robot/utils/islike.py index 41e3800e318..68136989798 100644 --- a/src/robot/utils/islike.py +++ b/src/robot/utils/islike.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3, string_types + import sys if sys.platform.startswith('java'): from java.lang import String @@ -22,15 +24,15 @@ from collections import Mapping except ImportError: # New in 2.6 Mapping = dict -try: +if PY3: + from collections import UserDict, UserString +else: from UserDict import UserDict from UserString import UserString -except ImportError: # Python 3 - from collections import UserDict, UserString def is_str_like(item, allow_java=False): - return (isinstance(item, (basestring, UserString)) or + return (isinstance(item, string_types + (UserString,)) or allow_java and isinstance(item, String)) diff --git a/src/robot/utils/markupwriters.py b/src/robot/utils/markupwriters.py index 75ed343e672..bac88c44b64 100644 --- a/src/robot/utils/markupwriters.py +++ b/src/robot/utils/markupwriters.py @@ -12,14 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3, string_types + import sys from .markuputils import html_escape, xml_escape, attribute_escape -PY3 = sys.version_info[0] == 3 - - class _MarkupWriter(object): def __init__(self, output, line_separator='\n', encoding='UTF-8'): @@ -31,7 +30,7 @@ def __init__(self, output, line_separator='\n', encoding='UTF-8'): :param encoding: Encoding to be used to encode all text written to the output file. If `None`, text will not be encoded. """ - if isinstance(output, basestring): + if isinstance(output, string_types): if PY3: output = open(output, 'w', encoding=encoding) else: diff --git a/src/robot/utils/match.py b/src/robot/utils/match.py index 515b2e97acf..eaac49d58fc 100644 --- a/src/robot/utils/match.py +++ b/src/robot/utils/match.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import string_types + import re from functools import partial @@ -63,7 +65,7 @@ def __init__(self, patterns=None, ignore=(), caseless=True, spaceless=True, def _ensure_list(self, patterns): if patterns is None: return [] - if isinstance(patterns, basestring): + if isinstance(patterns, string_types): return [patterns] return patterns diff --git a/src/robot/utils/misc.py b/src/robot/utils/misc.py index 542c627769b..a8783cc32fa 100644 --- a/src/robot/utils/misc.py +++ b/src/robot/utils/misc.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import integer_types, text_type as unicode + import inspect import sys @@ -72,7 +74,7 @@ def _isWordBoundary(prev, char, next): def plural_or_not(item): - count = item if isinstance(item, (int, long)) else len(item) + count = item if isinstance(item, integer_types) else len(item) return '' if count == 1 else 's' diff --git a/src/robot/utils/normalizing.py b/src/robot/utils/normalizing.py index c9e4fa9d14a..eb2e8614f10 100644 --- a/src/robot/utils/normalizing.py +++ b/src/robot/utils/normalizing.py @@ -131,7 +131,7 @@ def clear(self): self._keys.clear() def has_key(self, key): - return self.data.has_key(self._normalize(key)) + return self._normalize(key) in self.data __contains__ = has_key @@ -159,7 +159,7 @@ def iteritems(self): def popitem(self): if not self: raise KeyError('dictionary is empty') - key = self.iterkeys().next() + key = next(self.iterkeys()) return key, self.pop(key) def copy(self): diff --git a/src/robot/utils/robotenv.py b/src/robot/utils/robotenv.py index adcb1491e8b..712c865015f 100644 --- a/src/robot/utils/robotenv.py +++ b/src/robot/utils/robotenv.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import string_types, text_type as unicode + import os import sys @@ -65,7 +67,7 @@ def _get_env_var_from_java(name): from java.lang import String, System def _get_env_var_from_java(name): - name = name if isinstance(name, basestring) else unic(name) + name = name if isinstance(name, string_types) else unic(name) value_set_before_execution = System.getenv(name) if value_set_before_execution is None: return None diff --git a/src/robot/utils/robotinspect.py b/src/robot/utils/robotinspect.py index 7e5305ac0e0..05b332128ab 100644 --- a/src/robot/utils/robotinspect.py +++ b/src/robot/utils/robotinspect.py @@ -22,7 +22,7 @@ def is_java_init(init): return isinstance(init, PyReflectedConstructor) def is_java_method(method): - func = method.im_func if hasattr(method, 'im_func') else method + func = method.__func__ if hasattr(method, '__func__') else method return isinstance(func, PyReflectedFunction) else: diff --git a/src/robot/utils/robotpath.py b/src/robot/utils/robotpath.py index c53d7c788f1..b46cc6c9130 100644 --- a/src/robot/utils/robotpath.py +++ b/src/robot/utils/robotpath.py @@ -12,9 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3, text_type as unicode + import os import sys -import urllib +if PY3: + from urllib.request import pathname2url +else: + from urllib import pathname2url from robot.errors import DataError @@ -55,7 +60,7 @@ def abspath(path): if os.sep == '\\' and len(path) == 2 and path[1] == ':': return path + '\\' if not os.path.isabs(path): - path = os.path.join(os.getcwdu(), path) + path = os.path.join(os.getcwd() if PY3 else os.getcwdu(), path) return os.path.normpath(path) @@ -68,7 +73,7 @@ def get_link_path(target, base): Rationale: os.path.relpath is not available before Python 2.6 """ path = _get_pathname(target, base) - url = urllib.pathname2url(path.encode('UTF-8')) + url = pathname2url(path.encode('UTF-8')) if os.path.isabs(path): url = 'file:' + url # At least Jython seems to use 'C|/Path' and not 'C:/Path' diff --git a/src/robot/utils/robottime.py b/src/robot/utils/robottime.py index 56a3bc611f8..0f0886caac0 100644 --- a/src/robot/utils/robottime.py +++ b/src/robot/utils/robottime.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import integer_types + import time import datetime @@ -83,7 +85,7 @@ def _timestr_to_secs(timestr): return sign * (millis/1000 + secs + mins*60 + hours*60*60 + days*60*60*24) def _normalize_timestr(timestr): - if isinstance(timestr, (int, long, float)): + if isinstance(timestr, integer_types + (float,)): return timestr timestr = normalize(timestr) for item in 'milliseconds', 'millisecond', 'millis': @@ -171,7 +173,7 @@ def format_time(timetuple_or_epochsecs, daysep='', daytimesep=' ', timesep=':', Seconds after epoch can be either an integer or a float. """ - if isinstance(timetuple_or_epochsecs, (int, long, float)): + if isinstance(timetuple_or_epochsecs, integer_types + (float,)): timetuple = _get_timetuple(timetuple_or_epochsecs) else: timetuple = timetuple_or_epochsecs diff --git a/src/robot/utils/unic.py b/src/robot/utils/unic.py index a7970419ff1..e026fcaf7f3 100644 --- a/src/robot/utils/unic.py +++ b/src/robot/utils/unic.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import text_type as unicode + import sys # Need different unic implementations for different Pythons because: diff --git a/src/robot/utils/utf8reader.py b/src/robot/utils/utf8reader.py index b36f3f2b41d..f66e8dbeb40 100644 --- a/src/robot/utils/utf8reader.py +++ b/src/robot/utils/utf8reader.py @@ -12,13 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import string_types + from codecs import BOM_UTF8 class Utf8Reader(object): def __init__(self, path_or_file): - if isinstance(path_or_file, basestring): + if isinstance(path_or_file, string_types): self._file = open(path_or_file, 'rb') self._close = True else: diff --git a/src/robot/variables/isvar.py b/src/robot/variables/isvar.py index b33941291e7..0a27f020658 100644 --- a/src/robot/variables/isvar.py +++ b/src/robot/variables/isvar.py @@ -12,11 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import string_types + from .variablesplitter import VariableIterator def is_var(string): - if not isinstance(string, basestring): + if not isinstance(string, string_types): return False length = len(string) return length > 3 and string[0] in ['$','@'] and string.rfind('{') == 1 \ @@ -32,5 +34,5 @@ def is_list_var(string): def contains_var(string): - return bool(isinstance(string, basestring) and + return bool(isinstance(string, string_types) and VariableIterator(string, '$@')) diff --git a/src/robot/variables/variableassigner.py b/src/robot/variables/variableassigner.py index 8ca4811feb0..0f42097ebbe 100644 --- a/src/robot/variables/variableassigner.py +++ b/src/robot/variables/variableassigner.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import integer_types, string_types + import re from robot.errors import DataError @@ -63,7 +65,7 @@ def _split_extended_assign(self, name): return base.strip() + '}', attr[:-1].strip() def _variable_supports_extended_assign(self, var): - return not isinstance(var, (basestring, int, long, float)) + return not isinstance(var, string_types + integer_types + (float,)) def _is_valid_extended_attribute(self, attr): return self._valid_extended_attr.match(attr) is not None @@ -133,7 +135,7 @@ def _only_one_variable(self, variable, ret): return [(variable, ret)] def _convert_to_list(self, ret): - if isinstance(ret, basestring): + if isinstance(ret, string_types): self._raise_expected_list(ret) try: return list(ret) @@ -145,16 +147,16 @@ def _only_scalars(self, scalars, ret): if len(ret) < needed: self._raise_too_few_arguments(ret) if len(ret) == needed: - return zip(scalars, ret) - return zip(scalars[:-1], ret) + [(scalars[-1], ret[needed-1:])] + return list(zip(scalars, ret)) + return list(zip(scalars[:-1], ret)) + [(scalars[-1], ret[needed-1:])] def _scalars_and_list(self, scalars, list_, ret): if len(ret) < len(scalars): self._raise_too_few_arguments(ret) - return zip(scalars, ret) + [(list_, ret[len(scalars):])] + return list(zip(scalars, ret)) + [(list_, ret[len(scalars):])] def _raise_expected_list(self, ret): - typ = 'string' if isinstance(ret, basestring) else type(ret).__name__ + typ = 'string' if isinstance(ret, string_types) else type(ret).__name__ self._raise('Expected list-like object, got %s instead.' % typ) def _raise_too_few_arguments(self, ret): diff --git a/src/robot/variables/variables.py b/src/robot/variables/variables.py index eb219848e22..3076b475eed 100644 --- a/src/robot/variables/variables.py +++ b/src/robot/variables/variables.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import string_types, text_type as unicode + import re import inspect from functools import partial @@ -144,7 +146,7 @@ def _get_extended_var(self, name): expression = res.group(2) try: variable = self['${%s}' % base_name] - except DataError, err: + except DataError as err: raise DataError(err_pre + unicode(err)) try: return eval('_BASE_VAR_' + expression, {'_BASE_VAR_': variable}) @@ -214,7 +216,7 @@ def _replace_list(self, items): return results def _replace_variables_inside_possible_list_var(self, item): - if not (isinstance(item, basestring) and + if not (isinstance(item, string_types) and item.startswith('@{') and item.endswith('}')): return None var = VariableSplitter(item, self._identifiers) @@ -237,7 +239,7 @@ def replace_scalar(self, item): return self.replace_string(item, var) def _cannot_have_variables(self, item): - return not (isinstance(item, basestring) and '{' in item) + return not (isinstance(item, string_types) and '{' in item) def replace_string(self, string, splitter=None, ignore_errors=False): """Replaces variables from a string. Result is always a string.""" @@ -322,7 +324,7 @@ def _set_from_file(self, variables, overwrite=False): if name.startswith(list_prefix): name = '@{%s}' % name[len(list_prefix):] try: - if isinstance(value, basestring): + if isinstance(value, string_types): raise TypeError value = list(value) except TypeError: @@ -342,12 +344,12 @@ def set_from_variable_table(self, variables, overwrite=False): var.name, var.value, var.report_invalid_syntax) if overwrite or not self.contains(name): self.set(name, value) - except DataError, err: + except DataError as err: var.report_invalid_syntax(err) def _get_var_table_name_and_value(self, name, value, error_reporter): self._validate_var_name(name) - if is_scalar_var(name) and isinstance(value, basestring): + if is_scalar_var(name) and isinstance(value, string_types): value = [value] else: self._validate_var_is_not_scalar_list(name, value) @@ -413,7 +415,7 @@ def has_key(self, variable): def contains(self, variable, extended=False): if extended: - return self.has_key(variable) + return variable in self return utils.NormalizedDict.has_key(self, variable) @@ -426,7 +428,7 @@ def __init__(self, value, error_reporter): def resolve(self, name, variables): try: value = self._resolve(name, variables) - except DataError, err: + except DataError as err: self._error_reporter(unicode(err)) variables.pop(name) raise DataError("Non-existing variable '%s'." % name) diff --git a/src/robot/writer/datafilewriter.py b/src/robot/writer/datafilewriter.py index 47ee101ade9..56242ffe5ba 100644 --- a/src/robot/writer/datafilewriter.py +++ b/src/robot/writer/datafilewriter.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement +from six import PY3 + import os import sys @@ -97,7 +98,7 @@ def __enter__(self): if not self.output: # In Python 3, open with 'wb' only accepts bytes data, # which causes TypeErrors at other points - mode = 'w' if sys.version_info[0] == 3 else 'wb' + mode = 'w' if PY3 else 'wb' self.output = open(self._output_path(), mode) return self diff --git a/src/robot/writer/filewriters.py b/src/robot/writer/filewriters.py index 0010ebfa195..429ee3304b6 100644 --- a/src/robot/writer/filewriters.py +++ b/src/robot/writer/filewriters.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3 + import sys try: import csv @@ -131,7 +133,7 @@ def _get_writer(self, configuration): return csv.writer(configuration.output, dialect=dialect) def _write_row(self, row): - if sys.version_info[0] == 3: + if PY3: self._writer.writerow(list(row)) else: self._writer.writerow([self._encode(c) for c in row]) diff --git a/utest/run_utests.py b/utest/run_utests.py index d13e0992662..e8cc77d06db 100755 --- a/utest/run_utests.py +++ b/utest/run_utests.py @@ -54,14 +54,15 @@ ATESTDIR, PY3ATESTDIR, symlinks=True, ignore=lambda src, names: names if src == join(ATESTDIR, 'python3') else [] ) - status = subprocess.call( - ['2to3', '--no-diffs', '-n', '-w', - '-x', 'dict', - '-x', 'filter', - PY3DIR - ]) - if status: - sys.exit(status) + for testdir in [PY3UTESTDIR, PY3ATESTDIR]: + status = subprocess.call( + ['2to3', '--no-diffs', '-n', '-w', + '-x', 'dict', + '-x', 'filter', + testdir + ]) + if status: + sys.exit(status) do2to3 = False UTESTDIR = PY3UTESTDIR From 82b313fcfab1c4fdf5e654ee3b356b559a41a08b Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 11 Feb 2014 11:54:27 +0000 Subject: [PATCH 125/214] [python3] setup: Removed 2to3. --HG-- extra : transplant_source : Q%26%03%8DA%1A%D5%EEI%13%E3%A4%BB6%D3%BFq%A3%E8%7B --- setup.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/setup.py b/setup.py index 445a1d032f2..67557b575b5 100755 --- a/setup.py +++ b/setup.py @@ -77,9 +77,4 @@ package_data = {'robot': PACKAGE_DATA}, packages = PACKAGES, scripts = SCRIPTS, - use_2to3 = True, - use_2to3_exclude_fixers = ['lib2to3.fixes.fix_' + fix for fix in [ - 'dict', - 'filter', - ]], ) From 6405c68a95f81c23ce5c1cad648b4cab71047302 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 11 Feb 2014 12:18:28 +0000 Subject: [PATCH 126/214] [python3] README update. --HG-- extra : transplant_source : Ea%DF%28G%FCt%CAD%21%D4%BE%98e5%D7%1Db%7D- --- README.txt | 42 +++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/README.txt b/README.txt index 85486b31645..e18c54504d1 100644 --- a/README.txt +++ b/README.txt @@ -1,14 +1,14 @@ -Robot Framework with Python 3 compatibility -=========================================== +Robot Framework with Python 3.3+ compatibility +============================================== + +https://bitbucket.org/userzimmermann/robotframework-python3 - Forked from https://robotframework.googlecode.com -- Still compatible with all officially supported - Python 2.x platforms and versions, starting with 2.5 -- Not tested with Python 3 < 3.3 -- Invokes ``2to3`` in ``setup.py``, ``atest/run_atests.py`` - and ``utest/run_utests.py`` in addition to manual code changes -- Goal is to make code completely 2/3 compatible without the need for 2to3, - at the cost of dropping Python 2.5 support +- Compatible with **Python 2.7** +- ``robot`` code directly compatible (using six_) + ``utest`` and ``atest`` code still needs dynamic ``2to3`` + +:: _six: https://pypi.python.org/pypi/six Please report any issues to: @@ -18,6 +18,30 @@ You can look at this URL for a complete code diff: https://bitbucket.org/userzimmermann/robotframework-python3/compare/master..robot#diff + +Installation +------------ + +:: + + python setup.py install + +Or with `pip `_:: + + pip install . + +Or from `PyPI `_ +(Latest release ``2.8.3`` still completely relies on ``2to3`` +and doesn't use ``six``):: + + pip install robotframework-python3 + +Requirements +............ + +* `six `_ + + Differences in Python 3 ----------------------- From 779a6c705688c0ba3d9b284e7d838745e646077e Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 11 Feb 2014 12:28:03 +0000 Subject: [PATCH 127/214] [python3] README: Some formatting changes. --HG-- extra : transplant_source : %92g%13%F8%F1%1A%03%EC%D4%5EE%3DrY%D0%9A%95%D0%09%C3 --- README.txt | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/README.txt b/README.txt index e18c54504d1..a0e74792d27 100644 --- a/README.txt +++ b/README.txt @@ -6,9 +6,9 @@ https://bitbucket.org/userzimmermann/robotframework-python3 - Forked from https://robotframework.googlecode.com - Compatible with **Python 2.7** - ``robot`` code directly compatible (using six_) - ``utest`` and ``atest`` code still needs dynamic ``2to3`` +- `utest` and `atest` code still needs dynamic `2to3` -:: _six: https://pypi.python.org/pypi/six +.. _six: https://pypi.python.org/pypi/six Please report any issues to: @@ -31,8 +31,8 @@ Or with `pip `_:: pip install . Or from `PyPI `_ -(Latest release ``2.8.3`` still completely relies on ``2to3`` -and doesn't use ``six``):: +(Latest release **2.8.3** still completely relies on `2to3` +and doesn't use `six`):: pip install robotframework-python3 @@ -45,30 +45,32 @@ Requirements Differences in Python 3 ----------------------- -Python 3 makes a clear distinction between ``str`` for textual data -and ``bytes`` for binary data. +Python 3 makes a clear distinction between `str` for textual data +and `bytes` for binary data. This affects the Standard Test Libraries and their Keywords: -- ``str`` arguments don't work where ``bytes`` are expected, - like writing to binary file streams or comparing with other ``bytes``. -- ``bytes`` don't work where ``str`` is expected, - like writing to text mode streams or comparing with another ``str``. -- Reading from binary streams always returns ``bytes``. -- Reading from text streams always returns ``str``. +- `str` arguments don't work where `bytes` are expected, + like writing to binary file streams or comparing with other `bytes`. +- `bytes` don't work where `str` is expected, + like writing to text mode streams or comparing with another `str`. +- Reading from binary streams always returns `bytes`. +- Reading from text streams always returns `str`. -You can use the following keywords to explicitly create ``bytes``: +You can use the following keywords to explicitly create `bytes`: -- ``BuiltIn.Convert To Bytes`` -- ``String.Encode String To Bytes`` +- `BuiltIn.Convert To Bytes` +- `String.Encode String To Bytes` -I extended ``Process.Start Process`` with a ``binary_mode`` argument. +I extended `Process.Start Process` with a `binary_mode` argument. By default the process streams are opened in text mode. -You can change this with setting ``binary_mode=True``. +You can change this with:: -``Collections.Get Dictionary Keys`` normally sorts the keys. + binary_mode=True + +`Collections.Get Dictionary Keys` normally sorts the keys. I disabled key sorting in Python 3, because most builtin types are not comparable to each other. -This further affects ``Get Dictionary Values`` and ``Get Dictionary Items``. +This further affects `Get Dictionary Values` and `Get Dictionary Items`. I still need to find a better solution... Maybe imitate Python 2 sorting? Any suggestions? :) From ecc97f85369e9098ff29cc783ee6f40a4840a097 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 11 Feb 2014 12:33:57 +0000 Subject: [PATCH 128/214] [python3] README: oops :) --HG-- extra : transplant_source : %ED%1E%7C%29%F117%DD%E8%FA%C52%D0d%80%CC%0D_%FA0 --- README.txt | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/README.txt b/README.txt index a0e74792d27..f25da5be1c6 100644 --- a/README.txt +++ b/README.txt @@ -6,7 +6,7 @@ https://bitbucket.org/userzimmermann/robotframework-python3 - Forked from https://robotframework.googlecode.com - Compatible with **Python 2.7** - ``robot`` code directly compatible (using six_) -- `utest` and `atest` code still needs dynamic `2to3` +- *utest* and *atest* code still needs dynamic *2to3* .. _six: https://pypi.python.org/pypi/six @@ -31,8 +31,8 @@ Or with `pip `_:: pip install . Or from `PyPI `_ -(Latest release **2.8.3** still completely relies on `2to3` -and doesn't use `six`):: +(Latest release **2.8.3** still completely relies on *2to3* +and doesn't use *six*):: pip install robotframework-python3 @@ -45,36 +45,37 @@ Requirements Differences in Python 3 ----------------------- -Python 3 makes a clear distinction between `str` for textual data -and `bytes` for binary data. +Python 3 makes a clear distinction between *str* for textual data +and *bytes* for binary data. This affects the Standard Test Libraries and their Keywords: -- `str` arguments don't work where `bytes` are expected, - like writing to binary file streams or comparing with other `bytes`. -- `bytes` don't work where `str` is expected, - like writing to text mode streams or comparing with another `str`. -- Reading from binary streams always returns `bytes`. -- Reading from text streams always returns `str`. +- *str* arguments don't work where *bytes* are expected, + like writing to binary file streams or comparing with other *bytes*. +- *bytes* don't work where *str* is expected, + like writing to text mode streams or comparing with another *str*. +- Reading from binary streams always returns *bytes*. +- Reading from text streams always returns *str*. -You can use the following keywords to explicitly create `bytes`: +You can use the following keywords to explicitly create *bytes*: -- `BuiltIn.Convert To Bytes` -- `String.Encode String To Bytes` +- **BuiltIn.Convert To Bytes** +- **String.Encode String To Bytes** -I extended `Process.Start Process` with a `binary_mode` argument. +I extended **Process.Start Process** with a *binary_mode* argument. By default the process streams are opened in text mode. You can change this with:: binary_mode=True -`Collections.Get Dictionary Keys` normally sorts the keys. +**Collections.Get Dictionary Keys** normally sorts the keys. I disabled key sorting in Python 3, because most builtin types are not comparable to each other. -This further affects `Get Dictionary Values` and `Get Dictionary Items`. +This further affects **Get Dictionary Values** and **Get Dictionary Items**. I still need to find a better solution... Maybe imitate Python 2 sorting? Any suggestions? :) --- Stefan Zimmermann + +-- **Stefan Zimmermann** Robot Framework From f596e99958cc837407aae1b923ebd5c5a39f6cdb Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 11 Feb 2014 12:41:20 +0000 Subject: [PATCH 129/214] [python3] requirements.txt (six) + install_requires in setup. --HG-- extra : transplant_source : %AA%0C%1Ew%90FUe%7F%D4%C1%AB%03%C0%01%CF%C7%BD%AD%28 --- requirements.txt | 1 + setup.py | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000000..ffe2fce4989 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +six diff --git a/setup.py b/setup.py index 67557b575b5..5e03950aefd 100755 --- a/setup.py +++ b/setup.py @@ -56,6 +56,8 @@ if 'bdist_wininst' in sys.argv: SCRIPTS.append('robot_postinstall.py') +REQUIRES = open('requirements.txt').read() + setup( name = 'robotframework-python3', version = get_version(sep=''), @@ -77,4 +79,5 @@ package_data = {'robot': PACKAGE_DATA}, packages = PACKAGES, scripts = SCRIPTS, + install_requires = REQUIRES, ) From 8aeae99a4fa3ff8f822ffd7900af3c17f3164911 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 17 Feb 2014 22:19:33 +0000 Subject: [PATCH 130/214] [python3] utest: Direct Python 2.7/3.3+ compatibility (using six) --HG-- extra : transplant_source : 5%7E%0D3%85%05%F0%23%FB%B5%FD%D2_k%7Fr%C2P%F0%9E --- src/robot/parsing/htmlreader.py | 8 ++---- utest/api/test_exposed_api.py | 2 +- utest/api/test_run_and_rebot.py | 2 +- utest/htmldata/test_jsonwriter.py | 17 ++++++----- utest/model/test_itemlist.py | 4 ++- utest/model/test_keyword.py | 2 ++ utest/model/test_message.py | 2 ++ utest/model/test_metadata.py | 2 ++ utest/model/test_tags.py | 2 ++ utest/model/test_testcase.py | 2 ++ utest/model/test_testsuite.py | 2 ++ utest/output/test_filelogger.py | 2 +- utest/output/test_listeners.py | 38 +++++++++++++------------ utest/output/test_logger.py | 6 ++-- utest/parsing/test_htmlreader.py | 2 +- utest/parsing/test_populator.py | 6 ++-- utest/parsing/test_tsvreader.py | 17 +++++------ utest/reporting/test_jsmodelbuilders.py | 4 ++- utest/reporting/test_jswriter.py | 2 +- utest/reporting/test_reporting.py | 2 +- utest/reporting/test_stringcache.py | 4 +-- utest/resources/runningtestcase.py | 2 +- utest/result/test_resultbuilder.py | 4 +-- utest/result/test_resultmodel.py | 2 ++ utest/result/test_resultserializer.py | 12 +++----- utest/run_jasmine.py | 8 ++++-- utest/run_utests.py | 2 +- utest/running/test_handlers.py | 16 +++++------ utest/running/test_imports.py | 2 +- utest/running/test_running.py | 2 +- utest/running/test_signalhandler.py | 1 - utest/running/test_testlibrary.py | 28 +++++++++--------- utest/running/test_userhandlers.py | 6 +++- utest/running/test_userlibrary.py | 2 +- utest/running/thread_resources.py | 2 +- utest/utils/test_argumentparser.py | 2 ++ utest/utils/test_asserts.py | 12 +++++--- utest/utils/test_encoding.py | 2 ++ utest/utils/test_error.py | 2 +- utest/utils/test_etreesource.py | 7 ++--- utest/utils/test_htmlwriter.py | 11 +++---- utest/utils/test_importer_util.py | 3 +- utest/utils/test_islike.py | 12 ++++---- utest/utils/test_normalizing.py | 4 +-- utest/utils/test_robotenv.py | 2 ++ utest/utils/test_robotpath.py | 2 ++ utest/utils/test_setter.py | 7 +++-- utest/utils/test_unic.py | 2 +- utest/utils/test_utf8reader.py | 6 +--- utest/utils/test_xmlwriter.py | 1 - utest/writer/test_filewriters.py | 3 +- 51 files changed, 158 insertions(+), 137 deletions(-) diff --git a/src/robot/parsing/htmlreader.py b/src/robot/parsing/htmlreader.py index d6c57ed572a..37757e33642 100644 --- a/src/robot/parsing/htmlreader.py +++ b/src/robot/parsing/htmlreader.py @@ -16,12 +16,8 @@ import sys -if PY3: - from html.parser import HTMLParser - from html.entities import entitydefs -else: - from HTMLParser import HTMLParser - from htmlentitydefs import entitydefs +from six.moves.html_parser import HTMLParser +from six.moves.html_entities import entitydefs NON_BREAKING_SPACE = u'\xA0' diff --git a/utest/api/test_exposed_api.py b/utest/api/test_exposed_api.py index 6ea08a5e009..9add704c713 100644 --- a/utest/api/test_exposed_api.py +++ b/utest/api/test_exposed_api.py @@ -34,7 +34,7 @@ def test_result_writer(self): class TestTestSuiteBuilder(unittest.TestCase): misc = join(abspath(__file__), '..', '..', '..', 'atest', 'testdata', 'misc') def sources(misc): - return [join(misc, n) for n in 'pass_and_fail.txt', 'normal.txt'] + return [join(misc, n) for n in ('pass_and_fail.txt', 'normal.txt')] sources = sources(misc) def test_create_with_datasources_as_list(self): diff --git a/utest/api/test_run_and_rebot.py b/utest/api/test_run_and_rebot.py index 82bd4d37873..4aaeef2931b 100644 --- a/utest/api/test_run_and_rebot.py +++ b/utest/api/test_run_and_rebot.py @@ -4,7 +4,7 @@ import signal from os.path import abspath, dirname, join, exists, curdir from os import chdir -from StringIO import StringIO +from six.moves import StringIO from robot.utils.asserts import assert_equals, assert_true from robot.running import namespace diff --git a/utest/htmldata/test_jsonwriter.py b/utest/htmldata/test_jsonwriter.py index d020611787f..3f5dad01713 100644 --- a/utest/htmldata/test_jsonwriter.py +++ b/utest/htmldata/test_jsonwriter.py @@ -1,8 +1,6 @@ -import sys +from six import PY2 -PY3 = sys.version_info[0] == 3 - -from StringIO import StringIO +from six.moves import StringIO try: import json except ImportError: @@ -33,7 +31,7 @@ def test_dump_string(self): def test_dump_non_ascii_string(self): expected = u'"hyv\xe4"' - if not PY3: + if PY2: expected = expected.encode('UTF-8') self._test(u'hyv\xe4', expected) @@ -54,8 +52,9 @@ def test_dump_integer(self): self._test(1, '1') def test_dump_long(self): - self._test(12345678901234567890L, '12345678901234567890') - self._test(0L, '0') + self._test(12345678901234567890, '12345678901234567890') + if PY2: + self._test(long(0), '0') def test_dump_list(self): self._test([1, 2, True, 'hello', 'world'], '[1,2,true,"hello","world"]') @@ -67,7 +66,7 @@ def test_dump_tuple(self): def test_dump_dictionary(self): self._test({'key': 1}, '{"key":1}') - self._test({'nested': [-1L, {42: None}]}, '{"nested":[-1,{42:null}]}') + self._test({'nested': [-1, {42: None}]}, '{"nested":[-1,{42:null}]}') def test_dictionaries_are_sorted(self): self._test({'key': 1, 'hello': ['wor', 'ld'], 'z': 'a', 'a': 'z'}, @@ -88,7 +87,7 @@ def test_json_dump_mapping(self): if json: def test_against_standard_json(self): - data = ['\\\'\"\r\t\n' + ''.join(chr(i) for i in xrange(32, 127)), + data = ['\\\'\"\r\t\n' + ''.join(chr(i) for i in range(32, 127)), {'A': 1, 'b': 2, 'C': ()}, None, (1, 2, 3)] try: expected = json.dumps(data, sort_keys=True, diff --git a/utest/model/test_itemlist.py b/utest/model/test_itemlist.py index d0cf7ac5e73..511b775b344 100644 --- a/utest/model/test_itemlist.py +++ b/utest/model/test_itemlist.py @@ -1,3 +1,5 @@ +from six import text_type as unicode + import unittest from robot.utils.asserts import assert_equal, assert_true, assert_raises @@ -91,7 +93,7 @@ def test_truth(self): assert_true(ItemList(int, items=[1])) def test_clear(self): - items = ItemList(int, range(10)) + items = ItemList(int, items=list(range(10))) items.clear() assert_equal(len(items), 0) diff --git a/utest/model/test_keyword.py b/utest/model/test_keyword.py index dcab49714a4..4b147c8cccd 100644 --- a/utest/model/test_keyword.py +++ b/utest/model/test_keyword.py @@ -1,3 +1,5 @@ +from six import text_type as unicode + import sys import unittest from robot.utils.asserts import assert_equal, assert_true, assert_raises diff --git a/utest/model/test_message.py b/utest/model/test_message.py index ae24f83060c..d33886215ba 100644 --- a/utest/model/test_message.py +++ b/utest/model/test_message.py @@ -1,3 +1,5 @@ +from six import text_type as unicode + import sys import unittest diff --git a/utest/model/test_metadata.py b/utest/model/test_metadata.py index 20f1668123c..8504d453069 100644 --- a/utest/model/test_metadata.py +++ b/utest/model/test_metadata.py @@ -1,3 +1,5 @@ +from six import text_type as unicode + import sys import unittest diff --git a/utest/model/test_tags.py b/utest/model/test_tags.py index 4a12c89fe2d..c114fd2d871 100644 --- a/utest/model/test_tags.py +++ b/utest/model/test_tags.py @@ -1,3 +1,5 @@ +from six import text_type as unicode + import sys import unittest import sys diff --git a/utest/model/test_testcase.py b/utest/model/test_testcase.py index dda17f3b66f..32846fe4983 100644 --- a/utest/model/test_testcase.py +++ b/utest/model/test_testcase.py @@ -1,3 +1,5 @@ +from six import text_type as unicode + import sys import unittest from robot.utils.asserts import assert_equal, assert_raises diff --git a/utest/model/test_testsuite.py b/utest/model/test_testsuite.py index 09cae1b3a57..aa922a43895 100644 --- a/utest/model/test_testsuite.py +++ b/utest/model/test_testsuite.py @@ -1,3 +1,5 @@ +from six import text_type as unicode + import sys import unittest from robot.utils.asserts import assert_equal, assert_true, assert_raises diff --git a/utest/output/test_filelogger.py b/utest/output/test_filelogger.py index 4e04e562fc3..1a514ea075e 100644 --- a/utest/output/test_filelogger.py +++ b/utest/output/test_filelogger.py @@ -1,5 +1,5 @@ import unittest -from StringIO import StringIO +from six.moves import StringIO import time from robot import utils diff --git a/utest/output/test_listeners.py b/utest/output/test_listeners.py index e902bb1827f..7bb43f31a8a 100644 --- a/utest/output/test_listeners.py +++ b/utest/output/test_listeners.py @@ -1,3 +1,5 @@ +from __future__ import print_function + import unittest from robot.output.listeners import Listeners @@ -55,31 +57,31 @@ def xunit_file(self, path): self._out_file('XUnit', path) def _out_file(self, name, path): - print '%s: %s' % (name, path) + print('%s: %s' % (name, path)) class ListenAllOldStyle(ListenOutputs): def start_suite(self, name, doc): - print "SUITE START: %s '%s'" % (name, doc) + print("SUITE START: %s '%s'" % (name, doc)) def start_test(self, name, doc, tags): tags = ', '.join([ str(tag) for tag in tags ]) - print "TEST START: %s '%s' %s" % (name, doc, tags) + print("TEST START: %s '%s' %s" % (name, doc, tags)) def start_keyword(self, name, args): args = [ str(arg) for arg in args ] - print "KW START: %s %s" % (name, args) + print("KW START: %s %s" % (name, args)) def end_keyword(self, status): - print "KW END: %s" % (status) + print("KW END: %s" % (status)) def end_test(self, status, message): if status == 'PASS': - print 'TEST END: PASS' + print('TEST END: PASS') else: - print "TEST END: %s %s" % (status, message) + print("TEST END: %s %s" % (status, message)) def end_suite(self, status, message): - print 'SUITE END: %s %s' % (status, message) + print('SUITE END: %s %s' % (status, message)) def close(self): - print 'Closing...' + print('Closing...') class ListenAllNewStyle(ListenOutputs): @@ -87,24 +89,24 @@ class ListenAllNewStyle(ListenOutputs): ROBOT_LISTENER_API_VERSION = '2' def start_suite(self, name, attrs): - print "SUITE START: %s '%s'" % (name, attrs['doc']) + print("SUITE START: %s '%s'" % (name, attrs['doc'])) def start_test(self, name, attrs): - print "TEST START: %s '%s' %s" % (name, attrs['doc'], - ', '.join(attrs['tags'])) + print("TEST START: %s '%s' %s" % (name, attrs['doc'], + ', '.join(attrs['tags']))) def start_keyword(self, name, attrs): args = [ str(arg) for arg in attrs['args'] ] - print "KW START: %s %s" % (name, args) + print("KW START: %s %s" % (name, args)) def end_keyword(self, name, attrs): - print "KW END: %s" % attrs['status'] + print("KW END: %s" % attrs['status']) def end_test(self, name, attrs): if attrs['status'] == 'PASS': - print 'TEST END: PASS' + print('TEST END: PASS') else: - print "TEST END: %s %s" % (attrs['status'], attrs['message']) + print("TEST END: %s %s" % (attrs['status'], attrs['message'])) def end_suite(self, name, attrs): - print 'SUITE END: %s %s' % (attrs['status'], attrs['statistics']) + print('SUITE END: %s %s' % (attrs['status'], attrs['statistics'])) def close(self): - print 'Closing...' + print('Closing...') class InvalidListenerOldStyle: diff --git a/utest/output/test_logger.py b/utest/output/test_logger.py index db3fbe931de..c5f83fed903 100644 --- a/utest/output/test_logger.py +++ b/utest/output/test_logger.py @@ -127,7 +127,7 @@ def end_keyword(self, keyword): self.ended_keyword = keyword def test_console_logger_is_automatically_registered(self): logger = Logger() - assert_true(logger._loggers.all_loggers()[0].start_suite.im_class is CommandLineMonitor) + assert_true(logger._loggers.all_loggers()[0].start_suite.__self__.__class__ is CommandLineMonitor) def test_loggercollection_is_iterable(self): logger = Logger() @@ -151,7 +151,7 @@ def test_automatic_console_logger_can_be_disabled_after_registering_logger(self) logger.register_logger(mock) logger.unregister_console_logger() self._number_of_registered_loggers_should_be(1, logger) - assert_true(logger._loggers.all_loggers()[0].message.im_class is LoggerMock) + assert_true(logger._loggers.all_loggers()[0].message.__self__.__class__ is LoggerMock) def test_disabling_automatic_logger_multiple_times_has_no_effect(self): logger = Logger() @@ -168,7 +168,7 @@ def test_registering_console_logger_disables_automatic_console_logger(self): logger = Logger() logger.register_console_logger(width=42) self._number_of_registered_loggers_should_be(1, logger) - assert_equals(logger._loggers.all_loggers()[0].start_suite.im_self._writer._width, 42) + assert_equals(logger._loggers.all_loggers()[0].start_suite.__self__._writer._width, 42) def test_unregister_logger(self): logger1, logger2, logger3 = LoggerMock(), LoggerMock(), LoggerMock() diff --git a/utest/parsing/test_htmlreader.py b/utest/parsing/test_htmlreader.py index 9d99c0b52e4..ff70df81fd4 100644 --- a/utest/parsing/test_htmlreader.py +++ b/utest/parsing/test_htmlreader.py @@ -59,7 +59,7 @@ def test_process_invalid_table(self): assert_none(self.reader.populator.current) self.reader.feed(ROW_TEMPLATE % ('This', 'row', 'is ignored')) assert_equals(self.reader.state, self.reader.IGNORE) - assert_equals(len(self.reader.populator.tables.values()), 0) + assert_equals(len(self.reader.populator.tables), 0) self.reader.feed('') assert_equals(self.reader.state, self.reader.IGNORE) diff --git a/utest/parsing/test_populator.py b/utest/parsing/test_populator.py index 2fe2410a439..603e0240907 100644 --- a/utest/parsing/test_populator.py +++ b/utest/parsing/test_populator.py @@ -1,5 +1,7 @@ +from six import string_types + import unittest -from StringIO import StringIO +from six.moves import StringIO from robot.parsing.populators import FromFilePopulator, DataRow, FromDirectoryPopulator from robot.parsing.model import TestCaseFile @@ -50,7 +52,7 @@ def _assert_no_parsing_errors(self): assert_true(self._logger.value() == '', self._logger.value()) def _start_table(self, name): - if isinstance(name, basestring): + if isinstance(name, string_types): name = [name] return self._populator.start_table(name) diff --git a/utest/parsing/test_tsvreader.py b/utest/parsing/test_tsvreader.py index 8fc5b9a45f5..ed13624bb53 100644 --- a/utest/parsing/test_tsvreader.py +++ b/utest/parsing/test_tsvreader.py @@ -1,8 +1,5 @@ import unittest -try: - from io import BytesIO -except ImportError: # Python < 3 - from StringIO import StringIO as BytesIO +from io import BytesIO from robot.parsing.tsvreader import TsvReader from robot.parsing.model import TestCaseFile @@ -22,7 +19,7 @@ def tearDown(self): robot.parsing.populators.PROCESS_CURDIR = self._orig_curdir def test_start_table(self): - tsv = BytesIO('''*SettING*\t* Value *\t*V* + tsv = BytesIO(b'''*SettING*\t* Value *\t*V* ***Variable *Not*Table* @@ -30,13 +27,13 @@ def test_start_table(self): Keyword*\tNot a table because doesn't start with '*' *******************T*e*s*t*********C*a*s*e************\t***********\t******\t* -'''.encode()) +''') TsvReader().read(tsv, FromFilePopulator(self.tcf)) assert_equals(self.tcf.setting_table.name, 'SettING') assert_equals(self.tcf.setting_table.header, ['SettING','Value','V']) def test_rows(self): - tsv = BytesIO('''Ignored text before tables... + tsv = BytesIO(b'''Ignored text before tables... Mote\tignored\text *Setting*\t*Value*\t*Value* Document\tWhatever\t\t\\\t @@ -45,7 +42,7 @@ def test_rows(self): *Variable*\tWhatever \\ \\ 2 escaped spaces before and after \\ \\\t\\ \\ value \\ \\ -'''.encode()) +''') TsvReader().read(tsv, FromFilePopulator(self.tcf)) assert_equals(self.tcf.setting_table.doc.value, 'Whatever ') assert_equals(self.tcf.setting_table.default_tags.value, ['t1','t2','t3']) @@ -54,7 +51,7 @@ def test_rows(self): def test_quotes(self): - tsv = BytesIO('''*Variable*\t*Value* + tsv = BytesIO(b'''*Variable*\t*Value* ${v}\tHello ${v}\t"Hello" ${v}\t"""Hello""" @@ -63,7 +60,7 @@ def test_quotes(self): ${v}\t"""Hel "" """" lo""""""" ${v}\t"Hello ${v}\tHello" -'''.encode()) +''') TsvReader().read(tsv, FromFilePopulator(self.tcf)) actual = [variable for variable in self.tcf.variable_table.variables] expected = ['Hello','Hello','"Hello"','""Hello""','Hel"lo', diff --git a/utest/reporting/test_jsmodelbuilders.py b/utest/reporting/test_jsmodelbuilders.py index 5e6fa7fd37f..df59fc46c80 100644 --- a/utest/reporting/test_jsmodelbuilders.py +++ b/utest/reporting/test_jsmodelbuilders.py @@ -1,3 +1,5 @@ +from six import integer_types + import unittest from os.path import abspath, basename, dirname, join @@ -17,7 +19,7 @@ def remap(model, strings): if isinstance(model, StringIndex): return strings[model][1:] - elif isinstance(model, (int, long, type(None))): + elif isinstance(model, integer_types + (type(None),)): return model elif isinstance(model, tuple): return tuple(remap(item, strings) for item in model) diff --git a/utest/reporting/test_jswriter.py b/utest/reporting/test_jswriter.py index 9ecbd7c4e45..253b1290ca8 100644 --- a/utest/reporting/test_jswriter.py +++ b/utest/reporting/test_jswriter.py @@ -1,4 +1,4 @@ -from StringIO import StringIO +from six.moves import StringIO import unittest from robot.utils.asserts import assert_equals, assert_true diff --git a/utest/reporting/test_reporting.py b/utest/reporting/test_reporting.py index 546b132630d..bc13ba2b86f 100644 --- a/utest/reporting/test_reporting.py +++ b/utest/reporting/test_reporting.py @@ -1,4 +1,4 @@ -from StringIO import StringIO +from six.moves import StringIO import os import unittest diff --git a/utest/reporting/test_stringcache.py b/utest/reporting/test_stringcache.py index 0117e55d754..658bdeb0e68 100644 --- a/utest/reporting/test_stringcache.py +++ b/utest/reporting/test_stringcache.py @@ -12,7 +12,7 @@ class TestStringCache(unittest.TestCase): def setUp(self): # To make test reproducable log the random seed if test fails - self._seed = long(time.time() * 256) + self._seed = int(time.time() * 256) random.seed(self._seed) self.cache = StringCache() @@ -62,7 +62,7 @@ def test_to_string(self): assert_equals(str(value), '42') def test_long_values(self): - target = sys.maxint + 42 + target = sys.maxsize + 42 value = StringIndex(target) assert_equals(str(value), str(target)) assert_false(str(value).endswith('L')) diff --git a/utest/resources/runningtestcase.py b/utest/resources/runningtestcase.py index 04f04b00136..073a4531585 100644 --- a/utest/resources/runningtestcase.py +++ b/utest/resources/runningtestcase.py @@ -2,7 +2,7 @@ from os import remove from os.path import exists import unittest -from StringIO import StringIO +from six.moves import StringIO class RunningTestCase(unittest.TestCase): diff --git a/utest/result/test_resultbuilder.py b/utest/result/test_resultbuilder.py index 88426f9e10c..51ec16f3149 100644 --- a/utest/result/test_resultbuilder.py +++ b/utest/result/test_resultbuilder.py @@ -1,8 +1,6 @@ -from __future__ import with_statement - from os.path import join, dirname import unittest -from StringIO import StringIO +from six.moves import StringIO from robot.errors import DataError from robot.result import ExecutionResult diff --git a/utest/result/test_resultmodel.py b/utest/result/test_resultmodel.py index 17fe8cc6992..8dd9de78856 100644 --- a/utest/result/test_resultmodel.py +++ b/utest/result/test_resultmodel.py @@ -1,3 +1,5 @@ +from six import text_type as unicode + import unittest from robot.utils.asserts import assert_equal, assert_raises, assert_true, assert_false diff --git a/utest/result/test_resultserializer.py b/utest/result/test_resultserializer.py index 12f2329de01..2eaac7978ae 100644 --- a/utest/result/test_resultserializer.py +++ b/utest/result/test_resultserializer.py @@ -1,14 +1,10 @@ -from __future__ import with_statement +from six import PY3 + import sys import unittest -PY3 = sys.version_info[0] == 3 - -if PY3: - from io import StringIO, BytesIO -else: - from StringIO import StringIO - BytesIO = StringIO +from six.moves import StringIO +from io import BytesIO from robot.result import ExecutionResult from robot.reporting.outputwriter import OutputWriter diff --git a/utest/run_jasmine.py b/utest/run_jasmine.py index f668ab5623a..ffed205a357 100755 --- a/utest/run_jasmine.py +++ b/utest/run_jasmine.py @@ -1,5 +1,7 @@ #!/usr/bin/env python -import urllib2 +from __future__ import print_function + +from six.moves.urllib.request import urlopen import shutil import os from os.path import join, exists, dirname, abspath @@ -38,13 +40,13 @@ def download_jasmine_reporters(): return if not exists(EXT_LIB): os.mkdir(EXT_LIB) - reporter = urllib2.urlopen(JASMINE_REPORTER_URL) + reporter = urlopen(JASMINE_REPORTER_URL) with open(join(EXT_LIB, 'tmp.zip'), 'w') as temp: temp.write(reporter.read()) with open(join(EXT_LIB, 'tmp.zip'), 'r') as temp: ZipFile(temp).extractall(EXT_LIB) extraction_dir = glob(join(EXT_LIB, 'larrymyers-jasmine-reporters*'))[0] - print 'Extracting Jasmine-Reporters to', extraction_dir + print('Extracting Jasmine-Reporters to', extraction_dir) shutil.move(extraction_dir, join(EXT_LIB, 'jasmine-reporters')) diff --git a/utest/run_utests.py b/utest/run_utests.py index e8cc77d06db..48789320f2b 100755 --- a/utest/run_utests.py +++ b/utest/run_utests.py @@ -54,7 +54,7 @@ ATESTDIR, PY3ATESTDIR, symlinks=True, ignore=lambda src, names: names if src == join(ATESTDIR, 'python3') else [] ) - for testdir in [PY3UTESTDIR, PY3ATESTDIR]: + for testdir in [PY3ATESTDIR]: status = subprocess.call( ['2to3', '--no-diffs', '-n', '-w', '-x', 'dict', diff --git a/utest/running/test_handlers.py b/utest/running/test_handlers.py index 9927215a318..4abadd5d46d 100644 --- a/utest/running/test_handlers.py +++ b/utest/running/test_handlers.py @@ -68,7 +68,7 @@ def test_getarginfo_getattr(self): for handler in handlers: assert_true(handler.name in ['Foo','Bar','Zap']) assert_equals(handler.arguments.minargs, 0) - assert_equals(handler.arguments.maxargs, sys.maxint) + assert_equals(handler.arguments.maxargs, sys.maxsize) class TestDynamicHandlerCreation(unittest.TestCase): @@ -94,10 +94,10 @@ def test_invalid_doc_type(self): self._assert_fails('Return value must be string.', doc=True) def test_none_argspec(self): - self._assert_spec(None, maxargs=sys.maxint, vararg='varargs', kwarg=False) + self._assert_spec(None, maxargs=sys.maxsize, vararg='varargs', kwarg=False) def test_none_argspec_when_kwargs_supported(self): - self._assert_spec(None, maxargs=sys.maxint, vararg='varargs', kwarg='kwargs') + self._assert_spec(None, maxargs=sys.maxsize, vararg='varargs', kwarg='kwargs') def test_empty_argspec(self): self._assert_spec([]) @@ -114,23 +114,23 @@ def test_default_value_may_contain_equal_sign(self): self._assert_spec(['d=foo=bar'], 0, 1, ['d'], ['foo=bar']) def test_varargs(self): - self._assert_spec(['*vararg'], 0, sys.maxint, vararg='vararg') + self._assert_spec(['*vararg'], 0, sys.maxsize, vararg='vararg') def test_kwargs(self): self._assert_spec(['**kwarg'], 0, 0, kwarg='kwarg') def test_varargs_and_kwargs(self): self._assert_spec(['*vararg', '**kwarg'], - 0, sys.maxint, vararg='vararg', kwarg='kwarg') + 0, sys.maxsize, vararg='vararg', kwarg='kwarg') def test_integration(self): self._assert_spec(['arg', 'default=value'], 1, 2, ['arg', 'default'], ['value']) - self._assert_spec(['arg', 'default=value', '*var'], 1, sys.maxint, + self._assert_spec(['arg', 'default=value', '*var'], 1, sys.maxsize, ['arg', 'default'], ['value'], 'var') self._assert_spec(['arg', 'default=value', '**kw'], 1, 2, ['arg', 'default'], ['value'], None, 'kw') - self._assert_spec(['arg', 'default=value', '*var', '**kw'], 1, sys.maxint, + self._assert_spec(['arg', 'default=value', '*var', '**kw'], 1, sys.maxsize, ['arg', 'default'], ['value'], 'var', 'kw') def test_invalid_argspec_type(self): @@ -217,7 +217,7 @@ def test_arg_limits_with_varargs(self): method = handlers['a_%d_n' % count] handler = _JavaHandler(LibraryMock(), method.__name__, method) assert_equals(handler.arguments.minargs, count) - assert_equals(handler.arguments.maxargs, sys.maxint) + assert_equals(handler.arguments.maxargs, sys.maxsize) def test_arg_limits_with_defaults(self): # defaults i.e. multiple signatures diff --git a/utest/running/test_imports.py b/utest/running/test_imports.py index a9d46720da2..ad8321f673a 100644 --- a/utest/running/test_imports.py +++ b/utest/running/test_imports.py @@ -1,5 +1,5 @@ import unittest -from StringIO import StringIO +from six.moves import StringIO from robot.running import TestSuite from robot.utils.asserts import assert_equals, assert_raises_with_msg diff --git a/utest/running/test_running.py b/utest/running/test_running.py index c53770b3cfc..ec9156c8fa0 100644 --- a/utest/running/test_running.py +++ b/utest/running/test_running.py @@ -1,7 +1,7 @@ import sys import unittest import signal -from StringIO import StringIO +from six.moves import StringIO from os.path import abspath, dirname, normpath, join from robot.utils.asserts import assert_equals diff --git a/utest/running/test_signalhandler.py b/utest/running/test_signalhandler.py index 2733f243dfd..3ca435eba88 100644 --- a/utest/running/test_signalhandler.py +++ b/utest/running/test_signalhandler.py @@ -1,4 +1,3 @@ -from __future__ import with_statement import sys import signal import unittest diff --git a/utest/running/test_testlibrary.py b/utest/running/test_testlibrary.py index d971b71ff64..6848cb47c52 100644 --- a/utest/running/test_testlibrary.py +++ b/utest/running/test_testlibrary.py @@ -1,8 +1,8 @@ +from six import PY3, text_type as unicode + import unittest import sys -PY3 = sys.version_info[0] == 3 - from robot.running.testlibraries import (TestLibrary, _ClassLibrary, _ModuleLibrary, _DynamicLibrary) from robot.utils.asserts import * @@ -206,10 +206,10 @@ def test_extended_java_lib_with_no_init_and_contructor(self): self._test_init_handler('extendingjava.ExtendJavaLibWithConstructor', ['arg'], 1, 3) def test_extended_java_lib_with_init_and_no_constructor(self): - self._test_init_handler('extendingjava.ExtendJavaLibWithInit', [1,2,3], 0, sys.maxint) + self._test_init_handler('extendingjava.ExtendJavaLibWithInit', [1,2,3], 0, sys.maxsize) def test_extended_java_lib_with_init_and_constructor(self): - self._test_init_handler('extendingjava.ExtendJavaLibWithInitAndConstructor', ['arg'], 0, sys.maxint) + self._test_init_handler('extendingjava.ExtendJavaLibWithInitAndConstructor', ['arg'], 0, sys.maxsize) class TestVersion(unittest.TestCase): @@ -458,7 +458,7 @@ def test_get_keyword_doc_and_args_are_ignored_if_not_callable(self): lib = TestLibrary('classes.InvalidAttributeDynamicLibrary') assert_equals(len(lib.handlers), 5) assert_equals(lib.handlers['No Arg'].doc, '') - assert_handler_args(lib.handlers['No Arg'], 0, sys.maxint) + assert_handler_args(lib.handlers['No Arg'], 0, sys.maxsize) def test_handler_is_not_created_if_get_keyword_doc_fails(self): lib = TestLibrary('classes.InvalidGetDocDynamicLibrary') @@ -473,8 +473,8 @@ def test_arguments_without_kwargs(self): for name, (mina, maxa) in [('No Arg', (0, 0)), ('One Arg', (1, 1)), ('One or Two Args', (1, 2)), - ('Many Args', (0, sys.maxint)), - ('No Arg Spec', (0, sys.maxint))]: + ('Many Args', (0, sys.maxsize)), + ('No Arg Spec', (0, sys.maxsize))]: assert_handler_args(lib.handlers[name], mina, maxa) def test_arguments_with_kwargs(self): @@ -482,11 +482,11 @@ def test_arguments_with_kwargs(self): for name, (mina, maxa) in [('No Arg', (0, 0)), ('One Arg', (1, 1)), ('One or Two Args', (1, 2)), - ('Many Args', (0, sys.maxint))]: + ('Many Args', (0, sys.maxsize))]: assert_handler_args(lib.handlers[name], mina, maxa, kwargs=False) for name, (mina, maxa) in [('Kwargs', (0, 0)), - ('Varargs and Kwargs', (0, sys.maxint)), - ('No Arg Spec', (0, sys.maxint))]: + ('Varargs and Kwargs', (0, sys.maxsize)), + ('No Arg Spec', (0, sys.maxsize))]: assert_handler_args(lib.handlers[name], mina, maxa, kwargs=True) @@ -505,7 +505,7 @@ def test_arguments_without_kwargs(self): for name, (mina, maxa) in [('Java No Arg', (0, 0)), ('Java One Arg', (1, 1)), ('Java One or Two Args', (1, 2)), - ('Java Many Args', (0, sys.maxint))]: + ('Java Many Args', (0, sys.maxsize))]: self._assert_handler(lib, name, mina, maxa) def test_arguments_with_kwargs(self): @@ -513,16 +513,16 @@ def test_arguments_with_kwargs(self): for name, (mina, maxa) in [('Java No Arg', (0, 0)), ('Java One Arg', (1, 1)), ('Java One or Two Args', (1, 2)), - ('Java Many Args', (0, sys.maxint))]: + ('Java Many Args', (0, sys.maxsize))]: self._assert_handler(lib, name, mina, maxa) for name, (mina, maxa) in [('Java Kwargs', (0, 0)), - ('Java Varargs and Kwargs', (0, sys.maxint))]: + ('Java Varargs and Kwargs', (0, sys.maxsize))]: self._assert_handler(lib, name, mina, maxa, kwargs=True) def test_get_keyword_doc_and_args_are_ignored_if_not_callable(self): lib = TestLibrary('InvalidAttributeArgDocDynamicJavaLibrary') assert_equals(len(lib.handlers), 1) - assert_handler_args(lib.handlers['keyword'], 0, sys.maxint) + assert_handler_args(lib.handlers['keyword'], 0, sys.maxsize) def test_handler_is_not_created_if_get_keyword_doc_fails(self): lib = TestLibrary('InvalidSignatureArgDocDynamicJavaLibrary') diff --git a/utest/running/test_userhandlers.py b/utest/running/test_userhandlers.py index cc647026f54..36b8b5e4943 100644 --- a/utest/running/test_userhandlers.py +++ b/utest/running/test_userhandlers.py @@ -19,9 +19,13 @@ class FakeArgs(object): def __init__(self, args): self.value = args - def __nonzero__(self): + def __bool__(self): return bool(self.value) + #PY2: + def __nonzero__(self): + return self.__bool__() + def __iter__(self): return iter(self.value) diff --git a/utest/running/test_userlibrary.py b/utest/running/test_userlibrary.py index 57be71186bf..69363f61469 100644 --- a/utest/running/test_userlibrary.py +++ b/utest/running/test_userlibrary.py @@ -12,7 +12,7 @@ class UserHandlerStub: def __init__(self, kwdata, library): self.name = kwdata.name if kwdata.name == 'FAIL': - raise Exception, 'Expected failure' + raise Exception('Expected failure') class EmbeddedArgsTemplateStub: diff --git a/utest/running/thread_resources.py b/utest/running/thread_resources.py index 9f7c92cde1a..749567229d8 100644 --- a/utest/running/thread_resources.py +++ b/utest/running/thread_resources.py @@ -19,7 +19,7 @@ def returning(arg): return arg def failing(msg='xxx'): - raise MyException, msg + raise MyException(msg) if os.name == 'java': from java.lang import Error diff --git a/utest/utils/test_argumentparser.py b/utest/utils/test_argumentparser.py index 9482fc2b132..d179fbdb586 100644 --- a/utest/utils/test_argumentparser.py +++ b/utest/utils/test_argumentparser.py @@ -1,3 +1,5 @@ +from six import text_type as unicode + import unittest import os diff --git a/utest/utils/test_asserts.py b/utest/utils/test_asserts.py index da589c37732..edcc3aca190 100644 --- a/utest/utils/test_asserts.py +++ b/utest/utils/test_asserts.py @@ -1,3 +1,5 @@ +from six import PY3 + import unittest, sys if __name__ == "__main__": @@ -25,7 +27,7 @@ def __str__(self): def func(msg=None): if msg is not None: - raise ValueError, msg + raise ValueError(msg) class TestAsserts(unittest.TestCase): @@ -42,12 +44,12 @@ def test_fail_unless_raises_with_msg(self): try: assert_raises_with_msg(ValueError, 'msg', func) error('No AssertionError raised') - except AE, err: + except AE as err: assert_equal(str(err), 'ValueError not raised') try: assert_raises_with_msg(ValueError, 'msg1', func, 'msg2') error('No AssertionError raised') - except AE, err: + except AE as err: expected = "Correct exception but wrong message: msg1 != msg2" assert_equal(str(err), expected) @@ -62,7 +64,9 @@ def test_fail_unless_equal(self): assert_raises(AE, assert_equals, None, True) def test_fail_unless_equal_with_values_having_same_string_repr(self): - for val, type_ in [(1, 'number'), (1L, 'number'), (MyEqual(1), 'MyEqual')]: + if PY3: + long = int + for val, type_ in [(1, 'number'), (long(1), 'number'), (MyEqual(1), 'MyEqual')]: assert_raises_with_msg(AE, '1 (string) != 1 (%s)' % type_, fail_unless_equal, '1', val) assert_raises_with_msg(AE, '1.0 (number) != 1.0 (string)', diff --git a/utest/utils/test_encoding.py b/utest/utils/test_encoding.py index e8b0b418ac2..2241fe633d7 100644 --- a/utest/utils/test_encoding.py +++ b/utest/utils/test_encoding.py @@ -1,3 +1,5 @@ +from six import text_type as unicode + import unittest import sys diff --git a/utest/utils/test_error.py b/utest/utils/test_error.py index e985c621cd7..4b4783cba56 100644 --- a/utest/utils/test_error.py +++ b/utest/utils/test_error.py @@ -26,7 +26,7 @@ def test_get_error_details_python(self): try: if not msg: raise exception - raise exception, msg + raise exception(msg) except: message, details = get_error_details() assert_equals(message, get_error_message()) diff --git a/utest/utils/test_etreesource.py b/utest/utils/test_etreesource.py index 2e72596cc7c..d1536785726 100644 --- a/utest/utils/test_etreesource.py +++ b/utest/utils/test_etreesource.py @@ -1,17 +1,16 @@ -from __future__ import with_statement +from six import PY3, text_type as unicode + import os import sys import unittest -PY3 = sys.version_info[0] == 3 - from robot.utils.asserts import assert_equals, assert_raises, assert_true from robot.utils.etreewrapper import ETSource, ET from robot.errors import DataError IRONPYTHON = sys.platform == 'cli' PATH = os.path.join(os.path.dirname(__file__), 'test_etreesource.py') -STARTSWITH = 'from __future__' if not PY3 else '\nimport os' +STARTSWITH = 'from six import' class TestETSource(unittest.TestCase): diff --git a/utest/utils/test_htmlwriter.py b/utest/utils/test_htmlwriter.py index aacfb96c0aa..c6747cf933f 100644 --- a/utest/utils/test_htmlwriter.py +++ b/utest/utils/test_htmlwriter.py @@ -1,14 +1,11 @@ +from six import PY3 + import sys import os import unittest -PY3 = sys.version_info[0] == 3 - -if PY3: - from io import BytesIO, StringIO -else: - from StringIO import StringIO - BytesIO = StringIO +from six.moves import StringIO +from io import BytesIO from robot.utils import HtmlWriter from robot.utils.asserts import assert_equals diff --git a/utest/utils/test_importer_util.py b/utest/utils/test_importer_util.py index e7b4232185e..5d3780930e5 100644 --- a/utest/utils/test_importer_util.py +++ b/utest/utils/test_importer_util.py @@ -1,4 +1,5 @@ -from __future__ import with_statement +from six import text_type as unicode + import time import unittest import tempfile diff --git a/utest/utils/test_islike.py b/utest/utils/test_islike.py index 404f2023a79..c2bdfb87e7e 100644 --- a/utest/utils/test_islike.py +++ b/utest/utils/test_islike.py @@ -1,3 +1,5 @@ +from six import PY2, PY3 + import unittest import sys @@ -11,13 +13,13 @@ except ImportError: pass from array import array -try: +if PY3: + from collections import UserDict, UserList, UserString + MutableString = UserString +else: from UserDict import UserDict from UserList import UserList from UserString import UserString, MutableString -except ImportError: # Python 3 - from collections import UserDict, UserList, UserString - MutableString = UserString from robot.utils import is_dict_like, is_list_like, is_str_like from robot.utils.asserts import assert_equals @@ -58,7 +60,7 @@ def test_java_dict_likes_are_not_list_like(self): assert_equals(is_list_like(HashMap()), False) def test_other_iterables_are_list_like(self): - for thing in [[], (), set(), xrange(1), generator(), array('i'), UserList()]: + for thing in [[], (), set(), (range if PY3 else xrange)(1), generator(), array('i'), UserList()]: assert_equals(is_list_like(thing), True, thing) def test_others_are_not_list_like(self): diff --git a/utest/utils/test_normalizing.py b/utest/utils/test_normalizing.py index 833ab9d2e87..7cacefa886d 100644 --- a/utest/utils/test_normalizing.py +++ b/utest/utils/test_normalizing.py @@ -246,8 +246,8 @@ def test_keys_values_and_items_are_returned_in_same_order(self): for i, c in enumerate('abcdefghijklmnopqrstuvwxyz0123456789!"#%&/()=?'): nd[c.upper()] = i nd[c+str(i)] = 1 - assert_equals(nd.items(), zip(nd.keys(), nd.values())) - assert_equals(list(nd.iteritems()), zip(nd.iterkeys(), nd.itervalues())) + assert_equals(nd.items(), list(zip(nd.keys(), nd.values()))) + assert_equals(list(nd.iteritems()), list(zip(nd.iterkeys(), nd.itervalues()))) def test_cmp(self): self._verify_cmp(NormalizedDict(), NormalizedDict()) diff --git a/utest/utils/test_robotenv.py b/utest/utils/test_robotenv.py index 2ce27ecd29f..efd17ddde17 100644 --- a/utest/utils/test_robotenv.py +++ b/utest/utils/test_robotenv.py @@ -1,3 +1,5 @@ +from six import text_type as unicode + import unittest import os diff --git a/utest/utils/test_robotpath.py b/utest/utils/test_robotpath.py index c861ac18ca6..5aaebb205b0 100644 --- a/utest/utils/test_robotpath.py +++ b/utest/utils/test_robotpath.py @@ -1,3 +1,5 @@ +from six import text_type as unicode + import unittest import os diff --git a/utest/utils/test_setter.py b/utest/utils/test_setter.py index 5cc4e0e2a21..62ff4ec0f54 100644 --- a/utest/utils/test_setter.py +++ b/utest/utils/test_setter.py @@ -1,12 +1,14 @@ +from six import add_metaclass + import unittest from robot.utils.asserts import assert_equal, assert_raises from robot.utils.setter import setter, SetterAwareType +@add_metaclass(SetterAwareType) class ExampleWithSlots(object): __slots__ = [] - __metaclass__ = SetterAwareType @setter def attr(self, value): @@ -18,8 +20,9 @@ def with_doc(self, value): return value +@add_metaclass(type) class Example(ExampleWithSlots): - __metaclass__ = type + pass class TestSetter(unittest.TestCase): diff --git a/utest/utils/test_unic.py b/utest/utils/test_unic.py index fd331c923c4..2a5f62c2488 100644 --- a/utest/utils/test_unic.py +++ b/utest/utils/test_unic.py @@ -34,7 +34,7 @@ def test_with_array_containing_unicode_objects(self): def test_with_iterator(self): iterator = UnicodeJavaLibrary().javaIterator() assert_true('java.util' in unic(iterator)) - assert_true('Circle is 360' in iterator.next()) + assert_true('Circle is 360' in next(iterator)) def test_failure_in_toString(self): class ToStringFails(Object): diff --git a/utest/utils/test_utf8reader.py b/utest/utils/test_utf8reader.py index 5d8b2584662..4eff1534b77 100644 --- a/utest/utils/test_utf8reader.py +++ b/utest/utils/test_utf8reader.py @@ -1,9 +1,5 @@ -from __future__ import with_statement from codecs import BOM_UTF8 -try: - from io import BytesIO -except ImportError: # Python < 3 - from StringIO import StringIO as BytesIO +from io import BytesIO import os import tempfile import unittest diff --git a/utest/utils/test_xmlwriter.py b/utest/utils/test_xmlwriter.py index 4eb212457ab..d8a2750e74b 100644 --- a/utest/utils/test_xmlwriter.py +++ b/utest/utils/test_xmlwriter.py @@ -1,4 +1,3 @@ -from __future__ import with_statement import sys import os import unittest diff --git a/utest/writer/test_filewriters.py b/utest/writer/test_filewriters.py index ead6a5fe52e..1260577cdf6 100644 --- a/utest/writer/test_filewriters.py +++ b/utest/writer/test_filewriters.py @@ -1,7 +1,6 @@ -from __future__ import with_statement import os import unittest -from StringIO import StringIO +from six.moves import StringIO from robot.parsing import TestCaseFile from robot.parsing.model import TestCaseTable From c33d6a822f52fba4f768320a44ee66481b001012 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 18 Feb 2014 09:10:01 +0000 Subject: [PATCH 131/214] [python3] Some cleanup. --HG-- extra : transplant_source : %08%E0%0E%20U%8D%BBZ%B7H%21%7B%F5%20%EE%E2%D1%1B%C7J --- src/robot/libraries/BuiltIn.py | 4 +--- src/robot/output/filelogger.py | 9 +++++---- src/robot/writer/filewriters.py | 14 ++++++-------- utest/model/test_keyword.py | 4 ++-- utest/utils/test_asserts.py | 4 ++-- utest/utils/test_normalizing.py | 8 +++++--- utest/utils/test_robottime.py | 5 ++--- utest/utils/test_unic.py | 4 ++-- utest/utils/test_xmlwriter.py | 5 ++--- utest/variables/test_variables.py | 4 ++-- 10 files changed, 29 insertions(+), 32 deletions(-) diff --git a/src/robot/libraries/BuiltIn.py b/src/robot/libraries/BuiltIn.py index 12a1289280c..6f39c178711 100644 --- a/src/robot/libraries/BuiltIn.py +++ b/src/robot/libraries/BuiltIn.py @@ -2753,8 +2753,6 @@ def my_run_keyword_if(self, expression, name, *args): for name in [attr for attr in dir(_RunKeyword) if not attr.startswith('_')]: register_run_keyword('BuiltIn', getattr(_RunKeyword, name)) -try: +if PY2: del attr -except NameError: # Python 3 - pass del name diff --git a/src/robot/output/filelogger.py b/src/robot/output/filelogger.py index 07b88503863..aa866fad38a 100644 --- a/src/robot/output/filelogger.py +++ b/src/robot/output/filelogger.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3 + from robot.errors import DataError from .loggerhelper import AbstractLogger @@ -33,11 +35,10 @@ def message(self, msg): if self._is_logged(msg.level) and not self._writer.closed: entry = '%s | %s | %s\n' % (msg.timestamp, msg.level.ljust(5), msg.message) - encoded_entry = entry.encode('UTF-8') - try: - self._writer.write(encoded_entry) - except TypeError: # Python 3 + if PY3 and hasattr(self._writer, 'encoding'): self._writer.write(entry) + else: + self._writer.write(entry.encode('UTF-8')) def start_suite(self, suite): self.info("Started test suite '%s'" % suite.name) diff --git a/src/robot/writer/filewriters.py b/src/robot/writer/filewriters.py index 429ee3304b6..0e61bc488e8 100644 --- a/src/robot/writer/filewriters.py +++ b/src/robot/writer/filewriters.py @@ -89,11 +89,10 @@ def __init__(self, configuration): def _write_row(self, row): line = self._separator.join(row).rstrip() + self._line_separator - encoded_line = self._encode(line) - try: - self._output.write(encoded_line) - except TypeError: # Python 3 + if PY3 and hasattr(self._output, 'encoding'): self._output.write(line) + else: + self._output.write(self._encode(line)) class PipeSeparatedTxtWriter(_DataFileWriter): @@ -108,11 +107,10 @@ def _write_row(self, row): if row: row = '| ' + row + ' |' row += self._line_separator - encoded_row = self._encode(row) - try: - self._output.write(encoded_row) - except TypeError: # Python 3 + if PY3 and hasattr(self._output, 'encoding'): self._output.write(row) + else: + self._output.write(self._encode(row)) class TsvFileWriter(_DataFileWriter): diff --git a/utest/model/test_keyword.py b/utest/model/test_keyword.py index 4b147c8cccd..a84421d9031 100644 --- a/utest/model/test_keyword.py +++ b/utest/model/test_keyword.py @@ -1,4 +1,4 @@ -from six import text_type as unicode +from six import PY2, text_type as unicode import sys import unittest @@ -43,7 +43,7 @@ def test_unicode(self): assert_equal(unicode(self.ascii), 'Kekkonen') assert_equal(unicode(self.non_ascii), u'hyv\xe4 nimi') - if sys.version_info[0] < 3: + if PY2: def test_str(self): assert_equal(str(self.empty), '') assert_equal(str(self.ascii), 'Kekkonen') diff --git a/utest/utils/test_asserts.py b/utest/utils/test_asserts.py index edcc3aca190..c6963a4e09d 100644 --- a/utest/utils/test_asserts.py +++ b/utest/utils/test_asserts.py @@ -1,4 +1,6 @@ from six import PY3 +if PY3: + long = int import unittest, sys @@ -64,8 +66,6 @@ def test_fail_unless_equal(self): assert_raises(AE, assert_equals, None, True) def test_fail_unless_equal_with_values_having_same_string_repr(self): - if PY3: - long = int for val, type_ in [(1, 'number'), (long(1), 'number'), (MyEqual(1), 'MyEqual')]: assert_raises_with_msg(AE, '1 (string) != 1 (%s)' % type_, fail_unless_equal, '1', val) diff --git a/utest/utils/test_normalizing.py b/utest/utils/test_normalizing.py index 7cacefa886d..c2f58e0e821 100644 --- a/utest/utils/test_normalizing.py +++ b/utest/utils/test_normalizing.py @@ -1,8 +1,10 @@ +from six import PY3 + import unittest -try: - from UserDict import UserDict -except ImportError: # Python 3 +if PY3: from collections import UserDict +else: + from UserDict import UserDict from robot.utils import normalize, NormalizedDict from robot.utils.asserts import (assert_equals, assert_true, assert_false, diff --git a/utest/utils/test_robottime.py b/utest/utils/test_robottime.py index 7609102ae06..46cf239b26b 100644 --- a/utest/utils/test_robottime.py +++ b/utest/utils/test_robottime.py @@ -1,11 +1,10 @@ +from six import PY3 + import unittest -import sys import re import time import datetime -PY3 = sys.version_info[0] == 3 - from robot.utils.asserts import (assert_equal, assert_raises_with_msg, assert_true, assert_not_none) diff --git a/utest/utils/test_unic.py b/utest/utils/test_unic.py index 2a5f62c2488..95624f1a4f1 100644 --- a/utest/utils/test_unic.py +++ b/utest/utils/test_unic.py @@ -1,8 +1,8 @@ +from six import PY3 + import unittest import sys -PY3 = sys.version_info[0] == 3 - from robot.utils import unic, safe_repr from robot.utils.asserts import assert_equals, assert_true diff --git a/utest/utils/test_xmlwriter.py b/utest/utils/test_xmlwriter.py index d8a2750e74b..9974732337d 100644 --- a/utest/utils/test_xmlwriter.py +++ b/utest/utils/test_xmlwriter.py @@ -1,4 +1,5 @@ -import sys +from six import PY3 + import os import unittest import tempfile @@ -6,8 +7,6 @@ from robot.utils import XmlWriter, ET, ETSource from robot.utils.asserts import * -PY3 = sys.version_info[0] == 3 - PATH = os.path.join(tempfile.gettempdir(), 'test_xmlwriter.xml') diff --git a/utest/variables/test_variables.py b/utest/variables/test_variables.py index 5b06abf0df3..2ba64fc2203 100644 --- a/utest/variables/test_variables.py +++ b/utest/variables/test_variables.py @@ -1,8 +1,8 @@ +from six import PY3 + import unittest import sys -PY3 = sys.version_info[0] == 3 - from robot.variables import variables, is_list_var, is_scalar_var, is_var from robot.errors import * from robot import utils From 6301f875fb3f525b6bfcead92a3e2fe35dd8ab57 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 18 Feb 2014 10:51:30 +0000 Subject: [PATCH 132/214] [python3] atest: Python code directly 2.7/3.3+ compatible (using six) --HG-- extra : transplant_source : /F9%BE%88%1A%24%E8%94%3F%3B%AD%E9%92%1A%40%AF%F9%DF%B2 --- atest/genrunner.py | 4 +-- atest/resources/TestCheckerLibrary.py | 10 ++++--- atest/resources/unicode_vars.py | 2 ++ .../expected_output/ExpectedOutputLibrary.py | 1 - atest/robot/output/html_output_stats.py | 1 - atest/robot/running/ProcessManager.py | 8 ++--- atest/robot/tidy/TidyLib.py | 4 +-- .../dynamic_variables.py | 2 +- .../invalid_variable_file.py | 2 +- .../keywords/named_args/DynamicLibrary.py | 4 ++- atest/testdata/keywords/named_args/helper.py | 6 ++-- .../testdata/keywords/resources/MyLibrary1.py | 16 +++++----- .../testdata/keywords/resources/MyLibrary2.py | 10 +++---- atest/testdata/libdoc/DynamicLibrary.py | 4 ++- .../deprecated_os/files/prog.py | 2 +- .../files/stdout_and_stderr_prog.py | 2 +- .../deprecated_os/files/writable_prog.py | 2 +- .../operating_system/files/writable_prog.py | 2 +- .../process/files/non_terminable.py | 4 +-- .../standard_libraries/remote/arguments.py | 6 ++-- .../standard_libraries/remote/binaryresult.py | 7 ++--- .../remote/documentation.py | 7 ++++- .../standard_libraries/remote/remoteserver.py | 9 ++++-- .../standard_libraries/remote/simpleserver.py | 7 ++++- atest/testdata/test_libraries/HtmlPrintLib.py | 14 +++++---- .../testdata/test_libraries/ImportLogging.py | 6 ++-- atest/testdata/test_libraries/InitLogging.py | 6 ++-- .../test_libraries/LibUsingPyLogging.py | 2 ++ atest/testdata/test_libraries/MyLibFile.py | 2 +- .../PythonLibUsingTimestamps.py | 8 ++--- .../test_libraries/dir_for_libs/lib1/Lib.py | 2 +- .../test_libraries/dir_for_libs/lib2/Lib.py | 2 +- ...cLibraryWithKwargsSupportWithoutArgspec.py | 4 ++- .../DynamicLibraryWithoutArgspec.py | 9 ++++-- .../test_libraries/module_lib_with_all.py | 2 +- .../listeners/attributeverifyinglistener.py | 8 +++-- .../testlibs/BinaryDataLibrary.py | 8 ++--- .../testresources/testlibs/ExampleLibrary.py | 29 ++++++++++--------- .../testlibs/GetKeywordNamesLibrary.py | 6 ++-- .../testlibs/ImportRobotModuleTestLibrary.py | 12 ++++---- .../testlibs/RunKeywordLibrary.py | 5 +++- .../testresources/testlibs/UnicodeLibrary.py | 9 ++++-- .../testlibs/archive_src/ZipLib.py | 5 +++- atest/testresources/testlibs/bytelib.py | 4 +-- atest/testresources/testlibs/classes.py | 4 +-- atest/testresources/testlibs/libraryscope.py | 4 +-- .../testresources/testlibs/module_library.py | 4 +-- .../testresources/testlibs/newstyleclasses.py | 5 +++- .../testresources/testlibs/objecttoreturn.py | 2 +- 49 files changed, 170 insertions(+), 114 deletions(-) diff --git a/atest/genrunner.py b/atest/genrunner.py index db46de0042d..4a592769323 100755 --- a/atest/genrunner.py +++ b/atest/genrunner.py @@ -10,7 +10,7 @@ import sys if len(sys.argv) not in [2, 3] or not all(a.endswith('.txt') for a in sys.argv[1:]): - print __doc__ % basename(sys.argv[0]) + print(__doc__ % basename(sys.argv[0])) sys.exit(1) INPATH = abspath(sys.argv[1]) @@ -48,4 +48,4 @@ if test is not TESTS[-1]: output.write('\n') -print OUTPATH +print(OUTPATH) diff --git a/atest/resources/TestCheckerLibrary.py b/atest/resources/TestCheckerLibrary.py index 072a9e5cb35..6dd0ac6a88e 100644 --- a/atest/resources/TestCheckerLibrary.py +++ b/atest/resources/TestCheckerLibrary.py @@ -1,3 +1,5 @@ +from __future__ import print_function + import os import re @@ -27,7 +29,7 @@ class TestCheckerLibrary: def process_output(self, path): path = path.replace('/', os.sep) try: - print "Processing output '%s'" % path + print("Processing output '%s'" % path) result = Result(root_suite=NoSlotsTestSuite()) ExecutionResultBuilder(path).build(result) except: @@ -118,7 +120,7 @@ def check_suite_contains_tests(self, suite, *expected_names, **statuses): for test in actual_tests: norm_name = utils.normalize(test.name) if utils.MultiMatcher(expected_names).match(test.name): - print "Verifying test '%s'" % test.name + print("Verifying test '%s'" % test.name) status = statuses.get(norm_name) if status and ':' in status: status, message = status.split(':', 1) @@ -141,7 +143,7 @@ def should_not_contain_tests(self, suite, *test_names): raise AssertionError('Suite should not have contained test "%s"' % name) def should_contain_suites(self, suite, *expected_names): - print 'Suite has suites', suite.suites + print('Suite has suites', suite.suites) actual_names = [s.name for s in suite.suites] assert_equals(len(actual_names), len(expected_names), 'Wrong number of subsuites') for expected in expected_names: @@ -149,7 +151,7 @@ def should_contain_suites(self, suite, *expected_names): raise AssertionError('Suite %s not found' % expected) def should_contain_tags(self, test, *tags): - print 'Test has tags', test.tags + print('Test has tags', test.tags) assert_equals(len(test.tags), len(tags), 'Wrong number of tags') tags = sorted(tags, key=lambda s: s.lower().replace('_', '').replace(' ', '')) for act, exp in zip(test.tags, tags): diff --git a/atest/resources/unicode_vars.py b/atest/resources/unicode_vars.py index 7b1e563561c..f643f05a799 100644 --- a/atest/resources/unicode_vars.py +++ b/atest/resources/unicode_vars.py @@ -1,3 +1,5 @@ +from six import unichr + message_list = [ u'Circle is 360\u00B0', u'Hyv\u00E4\u00E4 \u00FC\u00F6t\u00E4', u'\u0989\u09C4 \u09F0 \u09FA \u099F \u09EB \u09EA \u09B9' ] diff --git a/atest/robot/cli/monitor/expected_output/ExpectedOutputLibrary.py b/atest/robot/cli/monitor/expected_output/ExpectedOutputLibrary.py index 124e1c224c1..5849dc5c301 100644 --- a/atest/robot/cli/monitor/expected_output/ExpectedOutputLibrary.py +++ b/atest/robot/cli/monitor/expected_output/ExpectedOutputLibrary.py @@ -1,4 +1,3 @@ -from __future__ import with_statement from os.path import abspath, dirname, join from fnmatch import fnmatchcase from operator import eq diff --git a/atest/robot/output/html_output_stats.py b/atest/robot/output/html_output_stats.py index 4627958bbe5..a3533f578ac 100644 --- a/atest/robot/output/html_output_stats.py +++ b/atest/robot/output/html_output_stats.py @@ -1,4 +1,3 @@ -from __future__ import with_statement from robot.api import logger diff --git a/atest/robot/running/ProcessManager.py b/atest/robot/running/ProcessManager.py index b56328f9093..24670508152 100644 --- a/atest/robot/running/ProcessManager.py +++ b/atest/robot/running/ProcessManager.py @@ -44,10 +44,10 @@ def get_stderr(self): def log_stdout_and_stderr(self): self.wait_until_finished() - print 'STDOUT:' - print self._stdout - print 'STDERR:' - print self._stderr + print('STDOUT:') + print(self._stdout) + print('STDERR:') + print(self._stderr) def wait_until_finished(self): if self._stdout is None: diff --git a/atest/robot/tidy/TidyLib.py b/atest/robot/tidy/TidyLib.py index 5c83c4fb134..01a45df47a4 100644 --- a/atest/robot/tidy/TidyLib.py +++ b/atest/robot/tidy/TidyLib.py @@ -1,4 +1,4 @@ -from __future__ import with_statement +from six import text_type as unicode import os import re @@ -29,7 +29,7 @@ def run_tidy(self, options, input, output=None, tidy=None): command.append(self._path(input)) if output: command.append(output) - print ' '.join(command) + print(' '.join(command)) with tempfile.TemporaryFile() as stdout: rc = call(command, stdout=stdout, stderr=STDOUT, cwd=ROBOT_SRC, shell=os.sep=='\\') diff --git a/atest/testdata/core/resources_and_variables/dynamic_variables.py b/atest/testdata/core/resources_and_variables/dynamic_variables.py index 11ea4c1de8b..0b2eab8e7ed 100644 --- a/atest/testdata/core/resources_and_variables/dynamic_variables.py +++ b/atest/testdata/core/resources_and_variables/dynamic_variables.py @@ -16,4 +16,4 @@ def get_variables(*args): return one_arg_vars if len(args) == 2: return None # this is invalid - raise Exception, 'Invalid arguments for get_variables' + raise Exception('Invalid arguments for get_variables') diff --git a/atest/testdata/core/resources_and_variables/invalid_variable_file.py b/atest/testdata/core/resources_and_variables/invalid_variable_file.py index 7266cc299f4..6d4ce295738 100644 --- a/atest/testdata/core/resources_and_variables/invalid_variable_file.py +++ b/atest/testdata/core/resources_and_variables/invalid_variable_file.py @@ -1 +1 @@ -raise Exception, 'This is an invalid variable file' \ No newline at end of file +raise Exception('This is an invalid variable file') diff --git a/atest/testdata/keywords/named_args/DynamicLibrary.py b/atest/testdata/keywords/named_args/DynamicLibrary.py index ec6c68cd9da..45adc179777 100644 --- a/atest/testdata/keywords/named_args/DynamicLibrary.py +++ b/atest/testdata/keywords/named_args/DynamicLibrary.py @@ -1,5 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +from six import string_types + from helper import pretty @@ -29,7 +31,7 @@ def run_keyword(self, kw_name, args): return self._pretty(*args) def _pretty(self, *args, **kwargs): - if all(isinstance(a, basestring) for a in args): + if all(isinstance(a, string_types) for a in args): return pretty(*args, **kwargs) return args[0] if len(args) == 1 else args diff --git a/atest/testdata/keywords/named_args/helper.py b/atest/testdata/keywords/named_args/helper.py index 45dd1b40bfd..0e9749b295a 100644 --- a/atest/testdata/keywords/named_args/helper.py +++ b/atest/testdata/keywords/named_args/helper.py @@ -1,9 +1,11 @@ +from six import string_types + from robot.libraries.BuiltIn import BuiltIn def get_result_or_error(*args): try: return BuiltIn().run_keyword(*args) - except Exception, err: + except Exception as err: return err.message def pretty(*args, **kwargs): @@ -12,6 +14,6 @@ def pretty(*args, **kwargs): return ', '.join(args + kwargs) def to_str(arg): - if isinstance(arg, basestring): + if isinstance(arg, string_types): return arg return '%s (%s)' % (arg, type(arg).__name__) diff --git a/atest/testdata/keywords/resources/MyLibrary1.py b/atest/testdata/keywords/resources/MyLibrary1.py index 26ccdf9015c..f63834e6e6b 100644 --- a/atest/testdata/keywords/resources/MyLibrary1.py +++ b/atest/testdata/keywords/resources/MyLibrary1.py @@ -1,16 +1,16 @@ class MyLibrary1: def keyword_only_in_library_1(self): - print "Keyword from library 1" + print("Keyword from library 1") def keyword_in_both_libraries(self): - print "Keyword from library 1" + print("Keyword from library 1") def keyword_in_all_resources_and_libraries(self): - print "Keyword from library 1" + print("Keyword from library 1") def keyword_everywhere(self): - print "Keyword from library 1" + print("Keyword from library 1") def keyword_in_tc_file_overrides_others(self): raise Exception("This keyword should not be called") @@ -19,14 +19,14 @@ def keyword_in_resource_overrides_libraries(self): raise Exception("This keyword should not be called") def comment(self): - print "Overrides keyword from BuiltIn library" + print("Overrides keyword from BuiltIn library") def copy_directory(self): - print "Overrides keyword from OperatingSystem library" + print("Overrides keyword from OperatingSystem library") def no_operation(self): - print "Overrides keyword from BuiltIn library" + print("Overrides keyword from BuiltIn library") def replace_string(self): - print "Overrides keyword from String library" + print("Overrides keyword from String library") return "I replace nothing!" diff --git a/atest/testdata/keywords/resources/MyLibrary2.py b/atest/testdata/keywords/resources/MyLibrary2.py index 6d25c7b9b9f..3b7f279a0e2 100644 --- a/atest/testdata/keywords/resources/MyLibrary2.py +++ b/atest/testdata/keywords/resources/MyLibrary2.py @@ -4,16 +4,16 @@ class MyLibrary2: def keyword_only_in_library_2(self): - print "Keyword from library 2" + print("Keyword from library 2") def keyword_in_both_libraries(self): - print "Keyword from library 2" + print("Keyword from library 2") def keyword_in_all_resources_and_libraries(self): - print "Keyword from library 2" + print("Keyword from library 2") def keyword_everywhere(self): - print "Keyword from library 2" + print("Keyword from library 2") def keyword_in_tc_file_overrides_others(self): raise Exception("This keyword should not be called") @@ -22,7 +22,7 @@ def keyword_in_resource_overrides_libraries(self): raise Exception("This keyword should not be called") def no_operation(self): - print "Overrides keyword from BuiltIn library" + print("Overrides keyword from BuiltIn library") def run_keyword_if(self, expression, name, *args): return BuiltIn().run_keyword_if(expression, name, *args) diff --git a/atest/testdata/libdoc/DynamicLibrary.py b/atest/testdata/libdoc/DynamicLibrary.py index 4b3fb3a087f..493128038dc 100644 --- a/atest/testdata/libdoc/DynamicLibrary.py +++ b/atest/testdata/libdoc/DynamicLibrary.py @@ -1,4 +1,6 @@ # coding=UTF-8 +from __future__ import print_function + class DynamicLibrary(object): """This is overwritten and not shown in docs""" @@ -11,7 +13,7 @@ def get_keyword_names(self): return ['0', 'Keyword 1', 'KW2', 'non ascii doc 42', 'no arg spec'] def run_keyword(self, name, args, kwargs): - print name, args + print(name, args) def get_keyword_arguments(self, name): if name == 'no arg spec': diff --git a/atest/testdata/standard_libraries/deprecated_os/files/prog.py b/atest/testdata/standard_libraries/deprecated_os/files/prog.py index 0104440586a..f408d6676a1 100644 --- a/atest/testdata/standard_libraries/deprecated_os/files/prog.py +++ b/atest/testdata/standard_libraries/deprecated_os/files/prog.py @@ -16,7 +16,7 @@ def output(msg, stream=sys.stdout): args = sys.argv[1:] try: rc = run(*args) - except Exception, err: + except Exception as err: output("Running failed with args %s for exception: %s" % (args, err)) rc = 255 sys.exit(rc) diff --git a/atest/testdata/standard_libraries/deprecated_os/files/stdout_and_stderr_prog.py b/atest/testdata/standard_libraries/deprecated_os/files/stdout_and_stderr_prog.py index 7013e981f12..689469d76ec 100644 --- a/atest/testdata/standard_libraries/deprecated_os/files/stdout_and_stderr_prog.py +++ b/atest/testdata/standard_libraries/deprecated_os/files/stdout_and_stderr_prog.py @@ -14,5 +14,5 @@ def output(msg, stream): args = sys.argv[1:] try: run(*args) - except Exception, err: + except Exception as err: output("Running failed with args %s for exception: %s" % (args, err)) diff --git a/atest/testdata/standard_libraries/deprecated_os/files/writable_prog.py b/atest/testdata/standard_libraries/deprecated_os/files/writable_prog.py index 6315ed7e7cf..ef6af9650b0 100644 --- a/atest/testdata/standard_libraries/deprecated_os/files/writable_prog.py +++ b/atest/testdata/standard_libraries/deprecated_os/files/writable_prog.py @@ -1,5 +1,5 @@ import sys -print sys.stdin.read().upper() +print(sys.stdin.read().upper()) diff --git a/atest/testdata/standard_libraries/operating_system/files/writable_prog.py b/atest/testdata/standard_libraries/operating_system/files/writable_prog.py index 6315ed7e7cf..ef6af9650b0 100644 --- a/atest/testdata/standard_libraries/operating_system/files/writable_prog.py +++ b/atest/testdata/standard_libraries/operating_system/files/writable_prog.py @@ -1,5 +1,5 @@ import sys -print sys.stdin.read().upper() +print(sys.stdin.read().upper()) diff --git a/atest/testdata/standard_libraries/process/files/non_terminable.py b/atest/testdata/standard_libraries/process/files/non_terminable.py index adf5547334e..a5af93d5423 100755 --- a/atest/testdata/standard_libraries/process/files/non_terminable.py +++ b/atest/testdata/standard_libraries/process/files/non_terminable.py @@ -1,10 +1,10 @@ import signal import time -print 'Starting non-terminable process' +print('Starting non-terminable process') def handler(signum, frame): - print 'Ignoring signal %d' % signum + print('Ignoring signal %d' % signum) signal.signal(signal.SIGTERM, handler) diff --git a/atest/testdata/standard_libraries/remote/arguments.py b/atest/testdata/standard_libraries/remote/arguments.py index ef5669f8665..bddfae021a8 100644 --- a/atest/testdata/standard_libraries/remote/arguments.py +++ b/atest/testdata/standard_libraries/remote/arguments.py @@ -1,5 +1,7 @@ +from six import string_types + import sys -from xmlrpclib import Binary +from six.moves.xmlrpc_client import Binary from remoteserver import RemoteServer @@ -74,7 +76,7 @@ def _format_args(self, *args, **kwargs): return ', '.join(self._type(a) for a in args) def _type(self, arg): - if not isinstance(arg, basestring): + if not isinstance(arg, string_types): return '%s (%s)' % (arg, type(arg).__name__) return arg diff --git a/atest/testdata/standard_libraries/remote/binaryresult.py b/atest/testdata/standard_libraries/remote/binaryresult.py index f15a908070f..0587191b420 100644 --- a/atest/testdata/standard_libraries/remote/binaryresult.py +++ b/atest/testdata/standard_libraries/remote/binaryresult.py @@ -1,12 +1,11 @@ +from six import PY3 + import sys -from xmlrpclib import Binary +from six.moves.xmlrpc_client import Binary from remoteserver import DirectResultRemoteServer -PY3 = sys.version_info[0] == 3 - - class BinaryResult(object): def blacheck(self, value): diff --git a/atest/testdata/standard_libraries/remote/documentation.py b/atest/testdata/standard_libraries/remote/documentation.py index 8259c2f7623..2a615436dcd 100644 --- a/atest/testdata/standard_libraries/remote/documentation.py +++ b/atest/testdata/standard_libraries/remote/documentation.py @@ -1,5 +1,10 @@ +from six import PY3 + import sys -from SimpleXMLRPCServer import SimpleXMLRPCServer +if PY3: + from xmlrpc.server import SimpleXMLRPCServer +else: + from SimpleXMLRPCServer import SimpleXMLRPCServer from remoteserver import announce_port diff --git a/atest/testdata/standard_libraries/remote/remoteserver.py b/atest/testdata/standard_libraries/remote/remoteserver.py index 143983f0b2b..e60ab90a1a2 100644 --- a/atest/testdata/standard_libraries/remote/remoteserver.py +++ b/atest/testdata/standard_libraries/remote/remoteserver.py @@ -1,6 +1,11 @@ +from six import PY3 + import inspect import sys -from SimpleXMLRPCServer import SimpleXMLRPCServer +if PY3: + from xmlrpc.server import SimpleXMLRPCServer +else: + from SimpleXMLRPCServer import SimpleXMLRPCServer class RemoteServer(SimpleXMLRPCServer): @@ -38,7 +43,7 @@ def get_keyword_arguments(self, name): def run_keyword(self, name, args, kwargs=None): try: result = getattr(self.library, name)(*args, **(kwargs or {})) - except AssertionError, err: + except AssertionError as err: return {'status': 'FAIL', 'error': str(err)} else: return {'status': 'PASS', diff --git a/atest/testdata/standard_libraries/remote/simpleserver.py b/atest/testdata/standard_libraries/remote/simpleserver.py index 50ddf77ed5a..368f04271af 100644 --- a/atest/testdata/standard_libraries/remote/simpleserver.py +++ b/atest/testdata/standard_libraries/remote/simpleserver.py @@ -1,5 +1,10 @@ +from six import PY3 + import sys -from SimpleXMLRPCServer import SimpleXMLRPCServer +if PY3: + from xmlrpc.server import SimpleXMLRPCServer +else: + from SimpleXMLRPCServer import SimpleXMLRPCServer from remoteserver import announce_port diff --git a/atest/testdata/test_libraries/HtmlPrintLib.py b/atest/testdata/test_libraries/HtmlPrintLib.py index 7c2d265db24..646e10b4348 100644 --- a/atest/testdata/test_libraries/HtmlPrintLib.py +++ b/atest/testdata/test_libraries/HtmlPrintLib.py @@ -1,14 +1,16 @@ +from __future__ import print_function + import sys def print_one_html_line(): - print '*HTML* Google' + print('*HTML* Google') def print_many_html_lines(): - print '*HTML* \n' - print '\n
0,00,1
1,01,1
' - print '*HTML*This is html
' - print '*INFO*This is not html
' + print('*HTML* \n') + print('\n
0,00,1
1,01,1
') + print('*HTML*This is html
') + print('*INFO*This is not html
') def print_html_to_stderr(): - print >> sys.stderr, '*HTML* Hello, stderr!!' + print('*HTML* Hello, stderr!!', file=sys.stderr) diff --git a/atest/testdata/test_libraries/ImportLogging.py b/atest/testdata/test_libraries/ImportLogging.py index d075035ac31..93d2adc85fa 100644 --- a/atest/testdata/test_libraries/ImportLogging.py +++ b/atest/testdata/test_libraries/ImportLogging.py @@ -1,8 +1,10 @@ +from __future__ import print_function + import sys from robot.api import logger -print '*WARN* Warning via stdout in import' -print >> sys.stderr, 'Info via stderr in import' +print('*WARN* Warning via stdout in import') +print('Info via stderr in import', file=sys.stderr) logger.warn('Warning via API in import') def keyword(): diff --git a/atest/testdata/test_libraries/InitLogging.py b/atest/testdata/test_libraries/InitLogging.py index 75fb0b1942e..f7d68b6b2bf 100644 --- a/atest/testdata/test_libraries/InitLogging.py +++ b/atest/testdata/test_libraries/InitLogging.py @@ -1,3 +1,5 @@ +from __future__ import print_function + import sys from robot.api import logger @@ -6,8 +8,8 @@ class InitLogging: def __init__(self): InitLogging.called += 1 - print '*WARN* Warning via stdout in init', self.called - print >> sys.stderr, 'Info via stderr in init', self.called + print('*WARN* Warning via stdout in init', self.called) + print('Info via stderr in init', self.called, file=sys.stderr) logger.warn('Warning via API in init %d' % self.called) def keyword(self): diff --git a/atest/testdata/test_libraries/LibUsingPyLogging.py b/atest/testdata/test_libraries/LibUsingPyLogging.py index daeeb57a6aa..bf69a255485 100644 --- a/atest/testdata/test_libraries/LibUsingPyLogging.py +++ b/atest/testdata/test_libraries/LibUsingPyLogging.py @@ -1,3 +1,5 @@ +from six import text_type as unicode + import logging import time import sys diff --git a/atest/testdata/test_libraries/MyLibFile.py b/atest/testdata/test_libraries/MyLibFile.py index 245c962a120..458fe6eccfd 100644 --- a/atest/testdata/test_libraries/MyLibFile.py +++ b/atest/testdata/test_libraries/MyLibFile.py @@ -1,2 +1,2 @@ def keyword_in_my_lib_file(): - print 'Here we go!!' \ No newline at end of file + print('Here we go!!') diff --git a/atest/testdata/test_libraries/PythonLibUsingTimestamps.py b/atest/testdata/test_libraries/PythonLibUsingTimestamps.py index 5ba195494b6..3aac1be0714 100644 --- a/atest/testdata/test_libraries/PythonLibUsingTimestamps.py +++ b/atest/testdata/test_libraries/PythonLibUsingTimestamps.py @@ -8,12 +8,12 @@ def timezone_correction(): def timestamp_as_integer(): t = 1308419034931 + timezone_correction() - print '*INFO:%d* Known timestamp' % t - print '*HTML:%d* Current' % int(time.time() * 1000) + print('*INFO:%d* Known timestamp' % t) + print('*HTML:%d* Current' % int(time.time() * 1000)) time.sleep(0.1) def timestamp_as_float(): t = 1308419034930.502342313 + timezone_correction() - print '*INFO:%f* Known timestamp' % t - print '*HTML:%f* Current' % float(time.time() * 1000) + print('*INFO:%f* Known timestamp' % t) + print('*HTML:%f* Current' % float(time.time() * 1000)) time.sleep(0.1) diff --git a/atest/testdata/test_libraries/dir_for_libs/lib1/Lib.py b/atest/testdata/test_libraries/dir_for_libs/lib1/Lib.py index 26ec87c6279..b0b3b304e39 100644 --- a/atest/testdata/test_libraries/dir_for_libs/lib1/Lib.py +++ b/atest/testdata/test_libraries/dir_for_libs/lib1/Lib.py @@ -1,7 +1,7 @@ class Lib: def hello(self): - print 'Hello from lib1' + print('Hello from lib1') def kw_from_lib1(self): pass diff --git a/atest/testdata/test_libraries/dir_for_libs/lib2/Lib.py b/atest/testdata/test_libraries/dir_for_libs/lib2/Lib.py index 3f764ae79a4..c72e1d0e16a 100644 --- a/atest/testdata/test_libraries/dir_for_libs/lib2/Lib.py +++ b/atest/testdata/test_libraries/dir_for_libs/lib2/Lib.py @@ -1,5 +1,5 @@ def hello(): - print 'Hello from lib2' + print('Hello from lib2') def kw_from_lib2(): pass diff --git a/atest/testdata/test_libraries/dynamic_libraries/DynamicLibraryWithKwargsSupportWithoutArgspec.py b/atest/testdata/test_libraries/dynamic_libraries/DynamicLibraryWithKwargsSupportWithoutArgspec.py index 47a72cf4d73..ebe43c8d71b 100644 --- a/atest/testdata/test_libraries/dynamic_libraries/DynamicLibraryWithKwargsSupportWithoutArgspec.py +++ b/atest/testdata/test_libraries/dynamic_libraries/DynamicLibraryWithKwargsSupportWithoutArgspec.py @@ -1,3 +1,5 @@ +from __future__ import print_function + from DynamicLibraryWithoutArgspec import DynamicLibraryWithoutArgspec @@ -7,4 +9,4 @@ def run_keyword(self, name, args, kwargs): return getattr(self, name)(*args, **kwargs) def do_something_with_kwargs(self, a, b=2, c=3, **kwargs): - print a, b, c, ' '.join('%s:%s' % (k, v) for k, v in kwargs.items()) + print(a, b, c, ' '.join('%s:%s' % (k, v) for k, v in kwargs.items())) diff --git a/atest/testdata/test_libraries/dynamic_libraries/DynamicLibraryWithoutArgspec.py b/atest/testdata/test_libraries/dynamic_libraries/DynamicLibraryWithoutArgspec.py index 165d9531292..943487a3779 100644 --- a/atest/testdata/test_libraries/dynamic_libraries/DynamicLibraryWithoutArgspec.py +++ b/atest/testdata/test_libraries/dynamic_libraries/DynamicLibraryWithoutArgspec.py @@ -1,3 +1,6 @@ +from __future__ import print_function + + class DynamicLibraryWithoutArgspec(object): def get_keyword_names(self): @@ -7,10 +10,10 @@ def run_keyword(self, name, args): return getattr(self, name)(*args) def do_something(self, x): - print x + print(x) def do_something_else(self, x, y=0): - print 'x: %s, y: %s' % (x, y) + print('x: %s, y: %s' % (x, y)) def do_something_third(self, a, b=2, c=3): - print a, b, c + print(a, b, c) diff --git a/atest/testdata/test_libraries/module_lib_with_all.py b/atest/testdata/test_libraries/module_lib_with_all.py index 04231f6b05c..a42014ef40f 100644 --- a/atest/testdata/test_libraries/module_lib_with_all.py +++ b/atest/testdata/test_libraries/module_lib_with_all.py @@ -12,4 +12,4 @@ def not_in_all(): attr_is_not_kw = 'Listed in __all__ but not a fuction' def _not_kw_even_if_listed_in_all(): - print 'Listed in __all__ but starts with an underscore' + print('Listed in __all__ but starts with an underscore') diff --git a/atest/testresources/listeners/attributeverifyinglistener.py b/atest/testresources/listeners/attributeverifyinglistener.py index 25b94d87601..e151bce1740 100644 --- a/atest/testresources/listeners/attributeverifyinglistener.py +++ b/atest/testresources/listeners/attributeverifyinglistener.py @@ -1,3 +1,5 @@ +from six import integer_types, string_types + import os ROBOT_LISTENER_API_VERSION = '2' @@ -5,7 +7,7 @@ OUTFILE = open(os.path.join(os.getenv('TEMPDIR'), 'listener_attrs.txt'), 'w') START_ATTRS = 'doc starttime ' END_ATTRS = START_ATTRS + 'endtime elapsedtime status ' -EXPECTED_TYPES = {'elapsedtime': (int, long), 'tags': list, 'args': list, +EXPECTED_TYPES = {'elapsedtime': integer_types, 'tags': list, 'args': list, 'metadata': dict, 'tests': list, 'suites': list, 'totaltests': int} @@ -37,11 +39,11 @@ def _verify_attrs(method_name, attrs, names): OUTFILE.write(method_name + '\n') if len(names) != len(attrs): OUTFILE.write('FAILED: wrong number of attributes\n') - OUTFILE.write('Expected: %s\nActual: %s\n' % (names, attrs.keys())) + OUTFILE.write('Expected: %s\nActual: %s\n' % (names, list(attrs.keys()))) return for name in names: value = attrs[name] - exp_type = EXPECTED_TYPES.get(name, basestring) + exp_type = EXPECTED_TYPES.get(name, string_types) if isinstance(value, exp_type): OUTFILE.write('PASSED | %s: %s\n' % (name, value)) else: diff --git a/atest/testresources/testlibs/BinaryDataLibrary.py b/atest/testresources/testlibs/BinaryDataLibrary.py index 3d82fbca2ac..ff5d3bfabc1 100644 --- a/atest/testresources/testlibs/BinaryDataLibrary.py +++ b/atest/testresources/testlibs/BinaryDataLibrary.py @@ -13,8 +13,8 @@ class BinaryDataLibrary: def print_bytes(self): """Prints all bytes in range 0-255. Many of them are control chars.""" for i in range(256): - print "*INFO* Byte %d: '%s'" % (i, chr(i)) - print "*INFO* All bytes printed successfully" + print("*INFO* Byte %d: '%s'" % (i, chr(i))) + print("*INFO* All bytes printed successfully") def raise_byte_error(self): raise AssertionError("Bytes 0, 10, 127, 255: '%s', '%s', '%s', '%s'" @@ -22,6 +22,6 @@ def raise_byte_error(self): def print_binary_data(self): bitmap = open(_BITMAP, 'rb') - print bitmap.read() + print(bitmap.read()) bitmap.close() - print "*INFO* Binary data printed successfully" + print("*INFO* Binary data printed successfully") diff --git a/atest/testresources/testlibs/ExampleLibrary.py b/atest/testresources/testlibs/ExampleLibrary.py index dc3240ddf27..4832417966d 100644 --- a/atest/testresources/testlibs/ExampleLibrary.py +++ b/atest/testresources/testlibs/ExampleLibrary.py @@ -1,3 +1,6 @@ +from __future__ import print_function +from six import text_type as unicode + import sys import time try: @@ -20,13 +23,13 @@ def print_(self, msg, stream='stdout'): def print_n_times(self, msg, count, delay=0): """Print given message n times""" for i in range(int(count)): - print msg + print(msg) self._sleep(delay) def print_many(self, *msgs): """Print given messages""" for msg in msgs: - print msg, + print(msg, end=' ') print def print_to_stdout_and_stderr(self, msg): @@ -35,9 +38,9 @@ def print_to_stdout_and_stderr(self, msg): def print_to_python_and_java_streams(self): import ExampleJavaLibrary - print '*INFO* First message to Python' + print('*INFO* First message to Python') getattr(ExampleJavaLibrary(), 'print')('*INFO* Second message to Java') - print '*INFO* Last message to Python' + print('*INFO* Last message to Python') def single_line_doc(self): """One line keyword documentation.""" @@ -56,7 +59,7 @@ def exception(self, name, msg=""): exception = getattr(exceptions, name) if msg is None: raise exception - raise exception, msg + raise exception(msg) def external_exception(self, name, msg): ObjectToReturn('failure').exception(name, msg) @@ -89,14 +92,14 @@ def check_attribute(self, name, expected): try: actual = getattr(self, utils.normalize(name)) except AttributeError: - raise AssertionError, "Attribute '%s' not set" % name + raise AssertionError("Attribute '%s' not set" % name) if not utils.eq(actual, expected): - raise AssertionError, "Attribute '%s' was '%s', expected '%s'" \ - % (name, actual, expected) + raise AssertionError("Attribute '%s' was '%s', expected '%s'" \ + % (name, actual, expected)) def check_attribute_not_set(self, name): if hasattr(self, utils.normalize(name)): - raise AssertionError, "Attribute '%s' should not be set" % name + raise AssertionError("Attribute '%s' should not be set" % name) def backslashes(self, count=1): return '\\' * int(count) @@ -104,17 +107,17 @@ def backslashes(self, count=1): def read_and_log_file(self, path, binary=False): mode = binary and 'rb' or 'r' _file = open(path, mode) - print _file.read() + print(_file.read()) _file.close() def print_control_chars(self): - print '\033[31mRED\033[m\033[32mGREEN\033[m' + print('\033[31mRED\033[m\033[32mGREEN\033[m') def long_message(self, line_length, line_count, chars='a'): line_length = int(line_length) line_count = int(line_count) msg = chars*line_length + '\n' - print msg*line_count + print(msg*line_count) def loop_forever(self, no_print=False): i = 0 @@ -122,7 +125,7 @@ def loop_forever(self, no_print=False): i += 1 self._sleep(1) if not no_print: - print 'Looping forever: %d' % i + print('Looping forever: %d' % i) def write_to_file_after_sleeping(self, path, sec, msg=None): f = open(path, 'w') diff --git a/atest/testresources/testlibs/GetKeywordNamesLibrary.py b/atest/testresources/testlibs/GetKeywordNamesLibrary.py index 2e6065327d0..98cb3ea1893 100644 --- a/atest/testresources/testlibs/GetKeywordNamesLibrary.py +++ b/atest/testresources/testlibs/GetKeywordNamesLibrary.py @@ -1,9 +1,11 @@ +from __future__ import print_function + from robot import utils def passing_handler(*args): for arg in args: - print arg, + print(arg, end=' ') return ', '.join(args) def failing_handler(*args): @@ -33,5 +35,5 @@ def __getattr__(self, name): def keyword_in_library_itself(self): msg = 'No need for __getattr__ here!!' - print msg + print(msg) return msg diff --git a/atest/testresources/testlibs/ImportRobotModuleTestLibrary.py b/atest/testresources/testlibs/ImportRobotModuleTestLibrary.py index 8697d60db5d..b32cb5d58ad 100644 --- a/atest/testresources/testlibs/ImportRobotModuleTestLibrary.py +++ b/atest/testresources/testlibs/ImportRobotModuleTestLibrary.py @@ -10,14 +10,14 @@ def import_logging(self): import logging except ImportError: if os.name == 'java': - print 'Could not import logging, which is OK in Jython!' + print('Could not import logging, which is OK in Jython!') return - raise AssertionError, 'Importing logging module failed with Python!' + raise AssertionError('Importing logging module failed with Python!') try: logger = logging.getLogger() except: - raise AssertionError, 'Wrong logging module imported!' - print 'Importing succeeded!' + raise AssertionError('Wrong logging module imported!') + print('Importing succeeded!') def importing_robot_module_directly_fails(self): try: @@ -28,10 +28,10 @@ def importing_robot_module_directly_fails(self): raise else: msg = "'import result' should have failed. Got it from '%s'. sys.path: %s" - raise AssertionError, msg % (serializing.__file__, sys.path) + raise AssertionError(msg % (serializing.__file__, sys.path)) def importing_robot_module_through_robot_succeeds(self): try: import robot.running except: - raise AssertionError, "'import robot.running' failed" + raise AssertionError("'import robot.running' failed") diff --git a/atest/testresources/testlibs/RunKeywordLibrary.py b/atest/testresources/testlibs/RunKeywordLibrary.py index 2623ff0af32..0807f94c488 100644 --- a/atest/testresources/testlibs/RunKeywordLibrary.py +++ b/atest/testresources/testlibs/RunKeywordLibrary.py @@ -1,3 +1,6 @@ +from __future__ import print_function + + class RunKeywordLibrary: ROBOT_LIBRARY_SCOPE = 'TESTCASE' @@ -16,7 +19,7 @@ def run_keyword(self, name, args): def _passes(self, args): for arg in args: - print arg, + print(arg, end=' ') return ', '.join(args) def _fails(self, args): diff --git a/atest/testresources/testlibs/UnicodeLibrary.py b/atest/testresources/testlibs/UnicodeLibrary.py index 82062a9f946..804c5e2d2c6 100644 --- a/atest/testresources/testlibs/UnicodeLibrary.py +++ b/atest/testresources/testlibs/UnicodeLibrary.py @@ -1,3 +1,6 @@ +from six import text_type as unicode + + messages = [ u'Circle is 360\u00B0', u'Hyv\u00E4\u00E4 \u00FC\u00F6t\u00E4', u'\u0989\u09C4 \u09F0 \u09FA \u099F \u09EB \u09EA \u09B9' ] @@ -8,16 +11,16 @@ class UnicodeLibrary: def print_unicode_strings(self): """Prints message containing unicode characters""" for msg in messages: - print '*INFO*' + msg + print('*INFO*' + msg) def print_and_return_unicode_object(self): """Prints unicode object and returns it.""" object = UnicodeObject() - print unicode(object) + print(unicode(object)) return object def raise_unicode_error(self): - raise AssertionError, ', '.join(messages) + raise AssertionError(', '.join(messages)) class UnicodeObject: diff --git a/atest/testresources/testlibs/archive_src/ZipLib.py b/atest/testresources/testlibs/archive_src/ZipLib.py index f757a027c6a..e7acc21c8ac 100644 --- a/atest/testresources/testlibs/archive_src/ZipLib.py +++ b/atest/testresources/testlibs/archive_src/ZipLib.py @@ -1,7 +1,10 @@ +from __future__ import print_function + + class ZipLib: def kw_from_zip(self, arg): - print '*INFO*', arg + print('*INFO*', arg) return arg * 2 diff --git a/atest/testresources/testlibs/bytelib.py b/atest/testresources/testlibs/bytelib.py index 20970ebc6f2..7e56e286c00 100644 --- a/atest/testresources/testlibs/bytelib.py +++ b/atest/testresources/testlibs/bytelib.py @@ -5,7 +5,7 @@ def in_return_value(): return 'ty\xf6paikka' def in_message(): - print '\xe4iti' + print('\xe4iti') def in_multiline_message(): - print '\xe4iti\nis\xe4' + print('\xe4iti\nis\xe4') diff --git a/atest/testresources/testlibs/classes.py b/atest/testresources/testlibs/classes.py index 5ce049266b2..9ecb5e9177f 100644 --- a/atest/testresources/testlibs/classes.py +++ b/atest/testresources/testlibs/classes.py @@ -144,7 +144,7 @@ def __init__(self): def get_keyword_names(self): return sorted(self._keywords.keys()) def run_keyword(self, name, *args): - print '*INFO* Executed keyword %s with arguments %s' % (name, args) + print('*INFO* Executed keyword %s with arguments %s' % (name, args)) def get_keyword_documentation(self, name): return self._keywords[name].doc def get_keyword_arguments(self, name): @@ -161,7 +161,7 @@ def __init__(self): def run_keyword(self, name, args, kwargs={}): argstr = ' '.join([str(a) for a in args] + ['%s:%s' % kv for kv in sorted(kwargs.items())]) - print '*INFO* Executed keyword %s with arguments %s' % (name, argstr) + print('*INFO* Executed keyword %s with arguments %s' % (name, argstr)) class _KeywordInfo: diff --git a/atest/testresources/testlibs/libraryscope.py b/atest/testresources/testlibs/libraryscope.py index ca50f11b837..d11b567c14b 100644 --- a/atest/testresources/testlibs/libraryscope.py +++ b/atest/testresources/testlibs/libraryscope.py @@ -9,8 +9,8 @@ def register(self, name): def should_be_registered(self, *expected): exp = dict([ (name, None) for name in expected ]) if self.registered != exp: - raise AssertionError, 'Wrong registered: %s != %s' \ - % (self.registered.keys(), exp.keys()) + raise AssertionError('Wrong registered: %s != %s' \ + % (list(self.registered.keys()), list(exp.keys()))) class Global(_BaseLib): diff --git a/atest/testresources/testlibs/module_library.py b/atest/testresources/testlibs/module_library.py index 86302aaa1b0..e8ce6b31c57 100644 --- a/atest/testresources/testlibs/module_library.py +++ b/atest/testresources/testlibs/module_library.py @@ -9,8 +9,8 @@ def failing(): raise AssertionError('This is a failing keyword from module library') def logging(): - print 'Hello from module library' - print '*WARN* WARNING!' + print('Hello from module library') + print('*WARN* WARNING!') def returning(): return 'Hello from module library' diff --git a/atest/testresources/testlibs/newstyleclasses.py b/atest/testresources/testlibs/newstyleclasses.py index 0165df02922..7dd4c54290c 100644 --- a/atest/testresources/testlibs/newstyleclasses.py +++ b/atest/testresources/testlibs/newstyleclasses.py @@ -1,3 +1,6 @@ +from six import add_metaclass + + class NewStyleClassLibrary(object): def mirror(self, arg): @@ -27,8 +30,8 @@ def method_in_metaclass(cls): pass +@add_metaclass(_MyMetaClass) class MetaClassLibrary(object): - __metaclass__ = _MyMetaClass def greet(self, name): return 'Hello %s!' % name diff --git a/atest/testresources/testlibs/objecttoreturn.py b/atest/testresources/testlibs/objecttoreturn.py index 0a2f1a82e28..030ede5b25c 100644 --- a/atest/testresources/testlibs/objecttoreturn.py +++ b/atest/testresources/testlibs/objecttoreturn.py @@ -13,4 +13,4 @@ def __str__(self): def exception(self, name, msg=""): exception = getattr(exceptions, name) - raise exception, msg + raise exception(msg) From 6cef2e8be9003584c00215445b96591d200d28c8 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 18 Feb 2014 10:52:07 +0000 Subject: [PATCH 133/214] [python3] Removed 2to3 stuff from run_utests.py --HG-- extra : transplant_source : %94%26V%C5%1E%9C%3F%E9M%10%92%9C%1F%C8%0C%95%C4%D7%BC4 --- utest/run_utests.py | 61 +++------------------------------------------ 1 file changed, 3 insertions(+), 58 deletions(-) diff --git a/utest/run_utests.py b/utest/run_utests.py index 48789320f2b..d4dc6cf2ca5 100755 --- a/utest/run_utests.py +++ b/utest/run_utests.py @@ -17,64 +17,9 @@ import sys import re import getopt -import shutil -import subprocess -from os.path import join, abspath, dirname -# Check for new working dir after 2to3: -if not 'UTESTDIR' in globals(): - # ==> still the original script before 2to3. - UTESTDIR = dirname(abspath(__file__)) -ROBOTDIR = join(UTESTDIR, '..', 'src', 'robot') -ATESTDIR = join(UTESTDIR, '..', 'atest') - -# If run with Python 3: -# - Copy src/robot/ and atest/ to atest/python3/ -# - Run 2to3 -# - Exec this file's copy in-place for actual testing - -# Is this script already the Python 3 copy? -if not 'do2to3' in globals(): - # ==> still the original. - do2to3 = True -if sys.version_info[0] == 3 and do2to3: - PY3DIR = join(UTESTDIR, 'python3') - PY3UTESTDIR = join(PY3DIR, 'utest') - PY3ATESTDIR = join(PY3DIR, 'atest') - - shutil.rmtree((PY3DIR), ignore_errors=True) - os.makedirs(join(PY3DIR, 'src')) - shutil.copytree(ROBOTDIR, join(PY3DIR, 'src', 'robot'), symlinks=True) - shutil.copytree( - UTESTDIR, PY3UTESTDIR, symlinks=True, - ignore=lambda src, names: names if src == PY3DIR else [] - ) - shutil.copytree( # ATESTDIR, PY3ATESTDIR, symlinks=True) - ATESTDIR, PY3ATESTDIR, symlinks=True, - ignore=lambda src, names: names if src == join(ATESTDIR, 'python3') else [] - ) - for testdir in [PY3ATESTDIR]: - status = subprocess.call( - ['2to3', '--no-diffs', '-n', '-w', - '-x', 'dict', - '-x', 'filter', - testdir - ]) - if status: - sys.exit(status) - - do2to3 = False - UTESTDIR = PY3UTESTDIR - - # Exec this file's Python 3 copy: - TESTRUNNER = join(UTESTDIR, 'run_utests.py') - exec(open(TESTRUNNER).read()) - sys.exit(0) - - -base = UTESTDIR -## base = os.path.abspath(os.path.normpath(os.path.split(sys.argv[0])[0])) +base = os.path.abspath(os.path.normpath(os.path.split(sys.argv[0])[0])) for path in ['../src', '../src/robot/libraries', '../src/robot', '../atest/testresources/testlibs' ]: path = os.path.join(base, path.replace('/', os.sep)) @@ -116,8 +61,8 @@ def parse_args(argv): ['help','verbose','quiet','doc']) if len(args) != 0: raise getopt.error('no arguments accepted, got %s' % (args)) - except getopt.error: - usage_exit(sys.exc_info()[1]) + except getopt.error as err: + usage_exit(err) for opt, value in options: if opt in ('-h','-H','-?','--help'): usage_exit() From a3242b11f82a59cfe02544f67ae693c4730e4acc Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 18 Feb 2014 10:52:40 +0000 Subject: [PATCH 134/214] [python3] Removed 2to3 stuff from run_atests.py. Only kept test data conversions. --HG-- extra : transplant_source : 2%E1%AC%07%ADp%F6%7E%ED%08%F6%CFw%1B/%BFf%FECL --- atest/run_atests.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/atest/run_atests.py b/atest/run_atests.py index 21b2be7c641..20ea5b18419 100755 --- a/atest/run_atests.py +++ b/atest/run_atests.py @@ -20,6 +20,8 @@ $ atest/run_atests.py /usr/bin/jython25 atest/robot/tags/tag_doc.txt """ +from six import PY3 + import re import os import shutil @@ -40,7 +42,6 @@ # If run with Python 3: # - Copy src/robot/ and atest/ to atest/python3/ -# - Run 2to3 # - Modify Python literals in Suite/Resource .txt files # - Exec this file's copy in-place for actual testing @@ -48,7 +49,7 @@ if not 'do2to3' in globals(): # ==> still the original. do2to3 = True -if sys.version_info[0] == 3 and do2to3: +if PY3 and do2to3: PY3DIR = join(CURDIR, 'python3') PY3ATESTDIR = join(PY3DIR, 'atest') @@ -59,19 +60,11 @@ CURDIR, join(PY3ATESTDIR), symlinks=True, ignore=lambda src, names: names if src == PY3DIR else [] ) - status = subprocess.call( - ['2to3', '--no-diffs', '-n', '-w', - '-x', 'dict', - '-x', 'filter', - PY3ATESTDIR - ]) - if status: - sys.exit(status) # Modify the Suite/Resource .txt files: - for atest_dirname in ['testdata', 'robot']: + for dirname in ['testdata', 'robot']: for dirpath, dirnames, filenames in os.walk( - join(PY3ATESTDIR, atest_dirname) + join(PY3ATESTDIR, dirname) ): for filename in filenames: if filename.endswith('.txt'): From 2044e6814a802bf2c23f76b4cc8b81b661d0b534 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 18 Feb 2014 19:17:14 +0000 Subject: [PATCH 135/214] [python3] Remote: Some compatibility updates. --HG-- extra : transplant_source : %D1%1Dx%20g%CC%02%B7%BE%A0%3E%9E%FF%3Cs%D9%FC%1Fl%CA --- src/robot/libraries/Remote.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/robot/libraries/Remote.py b/src/robot/libraries/Remote.py index 389093ae79a..e61b580ad61 100644 --- a/src/robot/libraries/Remote.py +++ b/src/robot/libraries/Remote.py @@ -18,10 +18,7 @@ import socket import sys import time -if PY3: - import xmlrpc.client as xmlrpclib -else: - import xmlrpclib +import six.moves.xmlrpc_client as xmlrpclib try: from xml.parsers.expat import ExpatError except ImportError: # No expat in IronPython 2.7 @@ -86,6 +83,7 @@ class ArgumentCoercer(object): def coerce(self, argument): for handles, handle in [(self._is_string, self._handle_string), + (self._is_bytes, self._handle_binary), #PY3 (self._is_number, self._pass_through), (is_list_like, self._coerce_list), (is_dict_like, self._coerce_dict), @@ -96,6 +94,10 @@ def coerce(self, argument): def _is_string(self, arg): return isinstance(arg, string_types) + #PY3: + def _is_bytes(self, arg): + return isinstance(arg, bytes) + def _is_number(self, arg): return isinstance(arg, integer_types + (float,)) @@ -112,7 +114,8 @@ def _contains_binary(self, arg): def _handle_binary(self, arg): try: if PY3: - arg = bytes(map(ord, arg)) + if not isinstance(arg, bytes): + arg = bytes(map(ord, arg)) else: arg = str(arg) except (ValueError, UnicodeError): From d86c7a6508f04241d634817463d7e093528e56d5 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 18 Feb 2014 20:14:04 +0000 Subject: [PATCH 136/214] [python3] run_atests: Write converted test data utf8 encoded. --HG-- extra : transplant_source : %00%84%A6%AE%03%DA%E7%EF%89%86c%E2g%E5%04%C1%A3%24%DCZ --- atest/run_atests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 atest/run_atests.py diff --git a/atest/run_atests.py b/atest/run_atests.py old mode 100755 new mode 100644 index 20ea5b18419..3fb8ae9e7ad --- a/atest/run_atests.py +++ b/atest/run_atests.py @@ -94,7 +94,7 @@ text) # Remove L suffixes from integer literals: text = re.sub(r'([1-9][0-9]+)L', r'\1', text) - with open(path, 'w') as f: + with open(path, 'w', encoding='utf8') as f: f.write(text) do2to3 = False From 8f8cfa9d2f23ed08e5f14d1140e7d25f951a2a1b Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 19 Feb 2014 09:27:12 +0000 Subject: [PATCH 137/214] [python3] Some Windows related fixes. --HG-- extra : transplant_source : p%02%DD%DF%E9%AE3%1B%25Y%E8%29%B6%DA%82%23O%CB%40%B1 --- src/robot/utils/argumentparser.py | 2 +- src/robot/utils/robotpath.py | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/robot/utils/argumentparser.py b/src/robot/utils/argumentparser.py index 52fa2c8f599..c3de520f429 100644 --- a/src/robot/utils/argumentparser.py +++ b/src/robot/utils/argumentparser.py @@ -305,7 +305,7 @@ def _split_pythonpath(self, paths): if drive: ret.append(drive) drive = '' - if len(item) == 1 and item in string.letters: + if len(item) == 1 and item in string.ascii_letters: drive = item else: ret.append(item) diff --git a/src/robot/utils/robotpath.py b/src/robot/utils/robotpath.py index b46cc6c9130..60c196ac739 100644 --- a/src/robot/utils/robotpath.py +++ b/src/robot/utils/robotpath.py @@ -16,10 +16,7 @@ import os import sys -if PY3: - from urllib.request import pathname2url -else: - from urllib import pathname2url +from six.moves.urllib.request import pathname2url from robot.errors import DataError @@ -73,7 +70,8 @@ def get_link_path(target, base): Rationale: os.path.relpath is not available before Python 2.6 """ path = _get_pathname(target, base) - url = pathname2url(path.encode('UTF-8')) + # Windows Python 3 pathname2url doesn't accept bytes + url = pathname2url(path if PY3 else path.encode('UTF-8')) if os.path.isabs(path): url = 'file:' + url # At least Jython seems to use 'C|/Path' and not 'C:/Path' From 1386efc36b9ca09764612445abef954ca8a0cfb0 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 2 Apr 2014 16:10:35 +0000 Subject: [PATCH 138/214] [python3] utils.unic: Separate PY3 _unic() with more workarounds. --HG-- extra : transplant_source : %EEt%04%BC%23%9AJ%88%BDhe%5B%FE%CC%26%F3%28%10%9F%10 --- src/robot/utils/unic.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/robot/utils/unic.py b/src/robot/utils/unic.py index e026fcaf7f3..7621b5aeda4 100644 --- a/src/robot/utils/unic.py +++ b/src/robot/utils/unic.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from six import text_type as unicode +from six import PY3, text_type as unicode import sys @@ -50,12 +50,6 @@ def unic(item, *args): def _unic(item, *args): - # First check if already unicode (Python 3 str) - # --> Python 3 will raise TypeError - # if trying to decode with str(item, *args) below. - # 2to3 changes `unicode` to `str`. - if type(item) is unicode: - return item # Based on a recipe from http://code.activestate.com/recipes/466341 try: return unicode(item, *args) @@ -68,6 +62,23 @@ def _unic(item, *args): except: return _unrepresentable_object(item) +if PY3: + def _unic(item, *args): + if isinstance(item, str): + return item + if isinstance(item, (bytes, bytearray)) and not args: + #TODO: Somehow nicer(?) + # First map byte values to unicode: + item = item.decode('latin') + # Then PY3 string_escape (==> bytes): + item = item.encode('unicode_escape') + # Finally to unicode again: + return item.decode('ascii') + try: + return str(item, *args) + except (UnicodeError, TypeError): + return _unrepresentable_object(item) + def safe_repr(item): try: From a5a7d8df7c0a13725f3bdc74b7f9882600762dda Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 2 Apr 2014 16:13:38 +0000 Subject: [PATCH 139/214] [python3] Remote: Simplfied ArgumentCoercer._handle_binary() --HG-- extra : transplant_source : %F5j%95%7Fmx%84%ADD%EE%16%A2%EE%F6P0%010%C1x --- src/robot/libraries/Remote.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/robot/libraries/Remote.py b/src/robot/libraries/Remote.py index e61b580ad61..a60d347778f 100644 --- a/src/robot/libraries/Remote.py +++ b/src/robot/libraries/Remote.py @@ -115,10 +115,10 @@ def _handle_binary(self, arg): try: if PY3: if not isinstance(arg, bytes): - arg = bytes(map(ord, arg)) + arg = bytes(arg, 'ascii') else: arg = str(arg) - except (ValueError, UnicodeError): + except UnicodeError: raise ValueError('Cannot represent %r as binary.' % arg) return xmlrpclib.Binary(arg) From d05df5b5463749eee437c56fd75df55fef79ac12 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 2 Apr 2014 16:16:55 +0000 Subject: [PATCH 140/214] [python3] atest: remote: Removed some debug code. --HG-- extra : transplant_source : %B5%83%FE%9C8%25%3Cc%7B%B8%20%F0%81gIpZlE6 --- atest/testdata/standard_libraries/remote/binaryresult.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/atest/testdata/standard_libraries/remote/binaryresult.py b/atest/testdata/standard_libraries/remote/binaryresult.py index 0587191b420..6cea3cc68bb 100644 --- a/atest/testdata/standard_libraries/remote/binaryresult.py +++ b/atest/testdata/standard_libraries/remote/binaryresult.py @@ -8,9 +8,6 @@ class BinaryResult(object): - def blacheck(self, value): - raise RuntimeError((type(value), str(value))) - def return_binary(self, *ordinals): return self._result(return_=self._binary(ordinals)) From 62d6aacb427d66c2c1799496d45830510833373b Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 2 Apr 2014 16:17:41 +0000 Subject: [PATCH 141/214] [python3] atest: remote: Binary.data instead of str() --HG-- extra : transplant_source : %17V%AF%7CGeB%88%D1%FE%19%00%5E%BE%3E%ECl%CA%BA%A6 --- atest/testdata/standard_libraries/remote/arguments.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atest/testdata/standard_libraries/remote/arguments.py b/atest/testdata/standard_libraries/remote/arguments.py index bddfae021a8..7fea511e61b 100644 --- a/atest/testdata/standard_libraries/remote/arguments.py +++ b/atest/testdata/standard_libraries/remote/arguments.py @@ -20,7 +20,7 @@ def _handle_binary(self, arg, required=True): if isinstance(arg, dict): return self._handle_binary_in_dict(arg) assert isinstance(arg, Binary) or not required, 'Non-binary argument' - return str(arg) if isinstance(arg, Binary) else arg + return arg.data if isinstance(arg, Binary) else arg def _handle_binary_in_list(self, arg): assert any(isinstance(a, Binary) for a in arg) From 3035b7ba8f6e0111f2f34760c645ab2a128e76be Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 2 Apr 2014 16:19:20 +0000 Subject: [PATCH 142/214] [python3] atest: remote/argument_coersion: b prefixes for byte strings. --HG-- extra : transplant_source : %F5%E1N%89%19%B8%23%AAsUSk%B9%0C%FE%FEy%1D%F1%B7 --- .../remote/argument_coersion.txt | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/atest/testdata/standard_libraries/remote/argument_coersion.txt b/atest/testdata/standard_libraries/remote/argument_coersion.txt index 3aff19736c9..acc8f3b2f9b 100644 --- a/atest/testdata/standard_libraries/remote/argument_coersion.txt +++ b/atest/testdata/standard_libraries/remote/argument_coersion.txt @@ -10,24 +10,27 @@ ${PORT} 8270 *** Test Cases *** String + b'Hello, world!' 'Hello, world!' u'hyv\\xe4 \\u2603' + b'\\x7f' '\\x7f' u'\\x7f\\x80\\xff' + b'' '' Newline and tab '\\t\\n\\r' '\\t\\n\\n' Binary - '\\x00\\x01\\x02' binary=yes - 'foo\\x00bar' binary=yes - u'\\x00\\x01' binary=yes + b'\\x00\\x01\\x02' binary=yes + b'foo\\x00bar' binary=yes + u'\\x00\\x01' b'\\x00\\x01' binary=yes Binary in non-ASCII range - '\\x00\\x01\\xe4' binary=yes - '\\x80' binary=yes - '\\xff' binary=yes + b'\\x00\\x01\\xe4' binary=yes + b'\\x80' binary=yes + b'\\xff' binary=yes Binary with too big Unicode characters [Template] Run Keyword And Expect Error @@ -65,7 +68,7 @@ Custom object with non-ASCII representation MyObject(u'hyv\\xe4') u'hyv\\xe4' Custom object with binary representation - MyObject('\\x00\\x01') '\\x00\\x01' binary=yes + MyObject('\\x00\\x01') b'\\x00\\x01' binary=yes List \[] @@ -79,10 +82,10 @@ List with non-ASCII values \[u'\\xe4', u'\\u2603'] List with non-ASCII byte values - \['\\x80', '\\xe4'] binary=yes + \[b'\\x80', b'\\xe4'] binary=yes List with binary values - \['\\x00', u'\\x01'] binary=yes + \['\\x00', u'\\x01'] [b'\\x00', b'\\x01'] binary=yes Nested list \[['a', 'b'], 3, [[[4], True]]] @@ -111,22 +114,22 @@ Dictionary with non-ASCII values {'2': u'\\u2603'} Dictionary with non-ASCII byte keys and values - {'\\x80': '\\x80'} {'\\\\x80': '\\x80'} binary=yes - {'\\xe4': '\\xe4'} {'\\\\xe4': '\\xe4'} binary=yes + {b'\\x80': b'\\x80'} {'\\\\x80': b'\\x80'} binary=yes + {b'\\xe4': b'\\xe4'} {'\\\\xe4': b'\\xe4'} binary=yes Dictionary with binary keys is not supported [Documentation] FAIL REGEXP: TypeError: unhashable (instance|type: 'Binary') {'\\x00': 'value'} Dictionary with binary values - {0: '\\x00', 1: u'\\x01'} {'0': '\\x00', '1': '\\x01'} binary=yes + {0: '\\x00', 1: u'\\x01'} {'0': b'\\x00', '1': b'\\x01'} binary=yes Nested dictionary {'a': 0, 'b': True, 'c': {'x': [1, 2, 3]}, '\\x7f': '\\x7f'} Mapping MyMapping() {} - MyMapping(a=1, b='\\x01') {'a': 1, 'b': '\\x01'} binary=yes + MyMapping(a=1, b='\\x01') {'a': 1, 'b': b'\\x01'} binary=yes MyMapping(a='one', b=2, c=[None, True]) {'a': 'one', 'b': 2, 'c': ['', True]} *** Keywords *** From f8e16a3e2749502e32e18601cbd500859d8c76de Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 2 Apr 2014 16:22:18 +0000 Subject: [PATCH 143/214] [python3] run_atests: No \x?? substitutions in standard_libraries/remote testdata. --HG-- extra : transplant_source : %E0Y%B4%5C%8D%B8%88%AE%9FD%B3%8A%3A%A2%E0%19%CB%8F%C58 --- atest/run_atests.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/atest/run_atests.py b/atest/run_atests.py index 3fb8ae9e7ad..93accf54dde 100644 --- a/atest/run_atests.py +++ b/atest/run_atests.py @@ -81,13 +81,14 @@ # Replace hex codes in strings # with actual unicode characters, # if not used to create bytes objects: - text = re.sub( - r'\\\\x([0-9a-f]{2})', - lambda match: ( - chr(int(match.group(1), 16)) - if match.group(1) >= '80' - else match.group(0)), - text) + if not 'remote' in dirpath: + text = re.sub( + r'\\\\x([0-9a-f]{2})', + lambda match: ( + chr(int(match.group(1), 16)) + if match.group(1) >= '80' + else match.group(0)), + text) text = re.sub( r'\\\\u([0-9a-f]{4})', lambda match: chr(int(match.group(1), 16)), From 89895b919640cc9f0cd436d4bf0dec2c8472ac37 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 2 Apr 2014 19:40:53 +0000 Subject: [PATCH 144/214] [python3] Remote: Binary.data instead of str() --HG-- extra : transplant_source : %DE%3D%27G%AB%D7%D4p%8B.%A5c%20h%81%C1%A3Y9%89 --- src/robot/libraries/Remote.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/robot/libraries/Remote.py b/src/robot/libraries/Remote.py index a60d347778f..424f02ee0a4 100644 --- a/src/robot/libraries/Remote.py +++ b/src/robot/libraries/Remote.py @@ -168,7 +168,7 @@ def _get(self, result, key, default=''): def _handle_binary(self, value): if isinstance(value, xmlrpclib.Binary): - return str(value) + return value.data if is_list_like(value): return [self._handle_binary(v) for v in value] if is_dict_like(value): From 595abca74ae11d6843fb4406257474b29801ab7b Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 3 Apr 2014 19:59:14 +0000 Subject: [PATCH 145/214] [python3] atest: test_libraries/error_msg_and_details: Reunified PY2/3 `Verify Python Traceback`. --HG-- extra : transplant_source : %DCe%19%8El%ACDG%BAw%FC%25_%92%BB%A6%B6%E5D%13 --- .../test_libraries/error_msg_and_details.txt | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/atest/robot/test_libraries/error_msg_and_details.txt b/atest/robot/test_libraries/error_msg_and_details.txt index 32d0a18e994..8fdeec042c9 100644 --- a/atest/robot/test_libraries/error_msg_and_details.txt +++ b/atest/robot/test_libraries/error_msg_and_details.txt @@ -50,15 +50,9 @@ Message Is Got Correctly If Java Exception Has 'null' Message Message And Internal Trace Are Removed From Details When Exception In Library [Template] NONE ${tc} = Verify Test Case And Error In Log Generic Failure foo != bar - Run on python 2.x - ... Verify Python Traceback ${tc.kws[0].msgs[1]} exception raise exception, msg - Run on python 3.x - ... Verify Python Traceback ${tc.kws[0].msgs[1]} exception raise exception(msg) + Verify Python Traceback ${tc.kws[0].msgs[1]} exception raise exception(msg) ${tc} = Verify Test Case And Error In Log Non Generic Failure FloatingPointError: Too Large A Number !! - Run on python 2.x - ... Verify Python Traceback ${tc.kws[0].msgs[1]} exception raise exception, msg - Run on python 3.x - ... Verify Python Traceback ${tc.kws[0].msgs[1]} exception raise exception(msg) + Verify Python Traceback ${tc.kws[0].msgs[1]} exception raise exception(msg) Message And Internal Trace Are Removed From Details When Exception In Java Library [Tags] jybot @@ -71,10 +65,7 @@ Message And Internal Trace Are Removed From Details When Exception In Java Libra Message and Internal Trace Are Removed From Details When Exception In External Code [Template] NONE ${tc} = Verify Test Case And Error In Log External Failure UnboundLocalError: Raised from an external object! - Run on python 2.x - ... Verify Python Traceback ${tc.kws[0].msgs[1]} external_exception ObjectToReturn('failure').exception(name, msg) exception raise exception, msg - Run on python 3.x - ... Verify Python Traceback ${tc.kws[0].msgs[1]} external_exception ObjectToReturn('failure').exception(name, msg) exception raise exception(msg) + Verify Python Traceback ${tc.kws[0].msgs[1]} external_exception ObjectToReturn('failure').exception(name, msg) exception raise exception(msg) Message and Internal Trace Are Removed From Details When Exception In External Java Code [Tags] jybot From ceb3acb74f6254c436d91f4002330f3ca5cc0f2d Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 5 May 2014 19:37:38 +0000 Subject: [PATCH 146/214] [python3] atest: ExampleLibrary.print_: write unic(msg) --HG-- extra : transplant_source : 3m%7B%0Bt%D3%5C%89%8F%FC%8E%0F%BC%B3%BF%9A%5C%7F%8E%8F --- atest/testresources/testlibs/ExampleLibrary.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/atest/testresources/testlibs/ExampleLibrary.py b/atest/testresources/testlibs/ExampleLibrary.py index 4832417966d..2bc48362ccf 100644 --- a/atest/testresources/testlibs/ExampleLibrary.py +++ b/atest/testresources/testlibs/ExampleLibrary.py @@ -1,11 +1,11 @@ from __future__ import print_function -from six import text_type as unicode +from six import PY2 import sys import time -try: +if PY2: import exceptions -except ImportError: # Python 3 +else: import builtins as exceptions from robot import utils @@ -18,7 +18,7 @@ class ExampleLibrary: def print_(self, msg, stream='stdout'): """Print given message to selected stream (stdout or stderr)""" out_stream = getattr(sys, stream) - out_stream.write(unicode(msg)) + out_stream.write(utils.unic(msg)) def print_n_times(self, msg, count, delay=0): """Print given message n times""" From 558677ce6dc5d4eaf79a8b705115891b6c8bc782 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 5 May 2014 19:39:29 +0000 Subject: [PATCH 147/214] [python3] Remote.run_keyword: write unic(result.output) --HG-- extra : transplant_source : %09-%F6%AA%ED%5C%94%F0%F5%F8%B6%7B%F2%F0%10%F5%CFm%23%22 --- src/robot/libraries/Remote.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/robot/libraries/Remote.py b/src/robot/libraries/Remote.py index 424f02ee0a4..e880f19622d 100644 --- a/src/robot/libraries/Remote.py +++ b/src/robot/libraries/Remote.py @@ -70,7 +70,7 @@ def run_keyword(self, name, args, kwargs): args = coercer.coerce(args) kwargs = coercer.coerce(kwargs) result = RemoteResult(self._client.run_keyword(name, args, kwargs)) - sys.stdout.write(result.output) + sys.stdout.write(unic(result.output)) if result.status != 'PASS': raise RemoteError(result.error, result.traceback, result.fatal, result.continuable) From 5fefbd5d40def6861ced4a02b678b3155ef2fa74 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 5 May 2014 19:49:19 +0000 Subject: [PATCH 148/214] [python3] README: Removed remaining 2to3 info. --HG-- extra : transplant_source : 7%26%01W%5D%AFJlP%DA%15%E1%B8%FB%5E%23%EDT%A9T --- README.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.txt b/README.txt index f25da5be1c6..236b1659a65 100644 --- a/README.txt +++ b/README.txt @@ -5,10 +5,6 @@ https://bitbucket.org/userzimmermann/robotframework-python3 - Forked from https://robotframework.googlecode.com - Compatible with **Python 2.7** -- ``robot`` code directly compatible (using six_) -- *utest* and *atest* code still needs dynamic *2to3* - -.. _six: https://pypi.python.org/pypi/six Please report any issues to: From cb62a74654360004f483d501e730300339c3adab Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 6 May 2014 09:22:05 +0000 Subject: [PATCH 149/214] [python3] setup: More Python version CLASSIFIERS. --HG-- extra : transplant_source : %C5P8%20B%94%BD%A65%3AN%84%8A%E2w%40%04%29%DE%89 --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index 5e03950aefd..0c962fdd8a3 100755 --- a/setup.py +++ b/setup.py @@ -32,7 +32,11 @@ License :: OSI Approved :: Apache Software License Operating System :: OS Independent Programming Language :: Python +Programming Language :: Python :: 2 +Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 +Programming Language :: Python :: 3.3 +Programming Language :: Python :: 3.4 Topic :: Software Development :: Testing """.strip().splitlines() PACKAGES = ['robot', 'robot.api', 'robot.conf', From 52d694af62f99e832b433515be5b8613c181f265 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 9 May 2014 10:44:50 +0000 Subject: [PATCH 150/214] [python3] package.py: print_function --HG-- extra : transplant_source : %19%9B%EA%27%89%AF7%262%89c%EF%80%E2%E6.%0Dep%12 --- package.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/package.py b/package.py index 9905d9cb22c..a3aea6dc9f7 100755 --- a/package.py +++ b/package.py @@ -34,7 +34,7 @@ package.py version trunk """ -from __future__ import with_statement +from __future__ import with_statement, print_function import sys import os from os.path import abspath, dirname, exists, join @@ -136,7 +136,7 @@ def _update_version(version_number, release_tag): vfile.close() # TODO: Fix before next final release #_update_pom_version(version_number, release_tag) - print 'Updated version to %s %s' % (version_number, release_tag) + print('Updated version to %s %s' % (version_number, release_tag)) def _update_pom_version(version_number, release_tag): version = '%s-%s' % (version_number, release_tag) @@ -149,18 +149,18 @@ def _update_pom_version(version_number, release_tag): def _keep_version(): sys.path.insert(0, ROBOT_PATH) from version import get_version - print 'Keeping version %s' % get_version() + print('Keeping version %s' % get_version()) def _clean(): - print 'Cleaning up...' + print('Cleaning up...') for path in [DIST_PATH, BUILD_PATH]: if exists(path): shutil.rmtree(path) def _verify_platform(version_number, release_tag=None): if release_tag == 'final' and os.sep != '\\': - print 'Final Windows installers can only be created in Windows.' - print 'Windows installer was not created.' + print('Final Windows installers can only be created in Windows.') + print('Windows installer was not created.') return False return True @@ -171,25 +171,25 @@ def _create_wininst(): _create('bdist_wininst --bitmap %s --install-script %s' % (BITMAP, INSTALL_SCRIPT), 'Windows installer') if os.sep != '\\': - print 'Warning: Windows installers created on other platforms may not' - print 'be exactly identical to ones created in Windows.' + print('Warning: Windows installers created on other platforms may not') + print('be exactly identical to ones created in Windows.') def _create(command, name): - print 'Creating %s...' % name + print('Creating %s...' % name) rc = os.system('%s %s %s' % (sys.executable, SETUP_PATH, command)) if rc != 0: - print 'Creating %s failed.' % name + print('Creating %s failed.' % name) sys.exit(rc) - print '%s created successfully.' % name.capitalize() + print('%s created successfully.' % name.capitalize()) def _announce(): - print 'Created:' + print('Created:') for path in os.listdir(DIST_PATH): - print abspath(join(DIST_PATH, path)) + print(abspath(join(DIST_PATH, path))) def jar(*version_info): jython_jar = _get_jython_jar() - print 'Using Jython %s' % jython_jar + print('Using Jython %s' % jython_jar) ver = version(*version_info) tmpdir = _create_tmpdir() try: @@ -200,11 +200,11 @@ def jar(*version_info): _overwrite_manifest(tmpdir, ver) try: jar_path = _create_jar_file(tmpdir, ver) - print 'Created %s based on %s' % (jar_path, jython_jar) + print('Created %s based on %s' % (jar_path, jython_jar)) except subprocess.CalledProcessError: - print "Unable to create jar! Check for jar command available at the command line." + print("Unable to create jar! Check for jar command available at the command line.") except subprocess.CalledProcessError: - print "Unable to compile java classes! Check for javac command available at the command line." + print("Unable to compile java classes! Check for javac command available at the command line.") shutil.rmtree(tmpdir) def _get_jython_jar(): @@ -216,14 +216,14 @@ def _get_jython_jar(): os.mkdir(lib_dir) dl_url = "http://search.maven.org/remotecontent?filepath=org/python/jython-standalone/%s/jython-standalone-%s.jar" \ % (JYTHON_VERSION, JYTHON_VERSION) - print 'Jython not found, going to download from %s' % dl_url + print('Jython not found, going to download from %s' % dl_url) urllib.urlretrieve(dl_url, jar_path) return jar_path def _compile_java_classes(tmpdir, jython_jar): source_files = [join(JAVA_SRC, f) for f in os.listdir(JAVA_SRC) if f.endswith('.java')] - print 'Compiling %d source files' % len(source_files) + print('Compiling %d source files' % len(source_files)) subprocess.check_call(['javac', '-d', tmpdir, '-target', '1.5', '-source', '1.5', '-cp', jython_jar] + source_files, shell=os.name=='nt') @@ -275,5 +275,5 @@ def _fill_jar(sourcedir, jarpath): try: globals()[sys.argv[1]](*sys.argv[2:]) except (KeyError, IndexError, TypeError, ValueError): - print __doc__ + print(__doc__) From 64755f3750f864e8ac262f8f2caffc6474a47c6d Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 9 May 2014 10:52:15 +0000 Subject: [PATCH 151/214] [python3] package.py: open vfile 'w', not 'wb' --HG-- extra : transplant_source : D%BE%9D%D6chJ4%1F%19%F47%C3%E7%CDS%7C%86D%BB --- package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.py b/package.py index a3aea6dc9f7..905d4af2732 100755 --- a/package.py +++ b/package.py @@ -131,7 +131,7 @@ def _verify_version(given, valid): def _update_version(version_number, release_tag): timestamp = '%d%02d%02d-%02d%02d%02d' % time.localtime()[:6] - vfile = open(VERSION_PATH, 'wb') + vfile = open(VERSION_PATH, 'w') vfile.write(VERSION_CONTENT % locals()) vfile.close() # TODO: Fix before next final release From 8a430ee651141f2f967274861c71b6ab89565372 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 9 May 2014 10:53:11 +0000 Subject: [PATCH 152/214] [python3] version: Updated TIMESTAMP --HG-- extra : transplant_source : %81j%95%E0%CC%B3%CF3n%E3%CB%CD%BA%82%DA%FC%0D%D3%296 --- src/robot/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/robot/version.py b/src/robot/version.py index 5b8a37a54fd..b248b9a3c61 100644 --- a/src/robot/version.py +++ b/src/robot/version.py @@ -4,7 +4,7 @@ VERSION = '2.8.4' RELEASE = 'final' -TIMESTAMP = '20140207-122231' +TIMESTAMP = '20140509-105233' def get_version(sep=' '): if RELEASE == 'final': From 8dc2a8b2af8f0860d2d6762005c49d4f201ff0ed Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 9 May 2014 11:03:55 +0000 Subject: [PATCH 153/214] [python3] MANIFEST: requirements.txt --HG-- extra : transplant_source : %02X%B0/%01A%08s%9A%5CVk9NAI%88bDh --- MANIFEST.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MANIFEST.in b/MANIFEST.in index 8d9d2db058b..99f48f63594 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,5 @@ +include requirements.txt + include src/bin/*ybot src/bin/*rebot src/bin/*.bat include src/robot/htmldata/*/*.html src/robot/htmldata/*/*.js src/robot/htmldata/*/*.css include MANIFEST.in LICENSE.txt COPYRIGHT.txt INSTALL.txt install.py AUTHORS.txt From 26c8b1ae64698d6b7a4565438f4037acd708b1ef Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 9 May 2014 11:03:58 +0000 Subject: [PATCH 154/214] [python3] README: Removed 2.8.3 release install info. --HG-- extra : transplant_source : %20%03%FF%EB8%CD%1D%9D%E1%D7%F3%01%B0I%F00sfL%91 --- README.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.txt b/README.txt index 236b1659a65..ec625d2f777 100644 --- a/README.txt +++ b/README.txt @@ -26,9 +26,7 @@ Or with `pip `_:: pip install . -Or from `PyPI `_ -(Latest release **2.8.3** still completely relies on *2to3* -and doesn't use *six*):: +Or from `PyPI `_:: pip install robotframework-python3 From 028b4e9d15dfc23660219f8438bff6a9010437fd Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 9 May 2014 16:12:09 +0000 Subject: [PATCH 155/214] [python3] Dialogs: six.moves.tkinter --HG-- extra : transplant_source : %21%C8N%E1E%15z%24%B0Z%7B%FD%2C%17%02%8Am%BA%A7%9A --- src/robot/libraries/dialogs_py.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/robot/libraries/dialogs_py.py b/src/robot/libraries/dialogs_py.py index 84d016fec70..680b930e6d1 100644 --- a/src/robot/libraries/dialogs_py.py +++ b/src/robot/libraries/dialogs_py.py @@ -12,16 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from six import PY3 - import sys from threading import currentThread -if PY3: - from tkinter import (Tk, Toplevel, Frame, Listbox, Label, Button, Entry, - BOTH, END, LEFT, W) -else: - from Tkinter import (Tk, Toplevel, Frame, Listbox, Label, Button, Entry, - BOTH, END, LEFT, W) +from six.moves.tkinter import ( + Tk, Toplevel, Frame, Listbox, Label, Button, Entry, BOTH, END, LEFT, W) class _TkDialog(Toplevel): From be2e3b82a7003d055a3d7bd003beec2fe7103cab Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 4 Sep 2014 17:16:18 +0000 Subject: [PATCH 156/214] [python3] _BaseSettings: sys.maxint-->maxsize --HG-- extra : transplant_source : pL%D8%7Ef%5Ce%FD/%7F%B9f%86%AF%F1%B73%E78Q --- src/robot/conf/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/robot/conf/settings.py b/src/robot/conf/settings.py index 79e83766b65..b4273667e3e 100644 --- a/src/robot/conf/settings.py +++ b/src/robot/conf/settings.py @@ -163,7 +163,7 @@ def _process_randomize_value(self, original): if ':' in value: value, seed = value.split(':', 1) else: - seed = random.randint(0, sys.maxint) + seed = random.randint(0, sys.maxsize) if value in ('test', 'suite'): value += 's' if value not in ('tests', 'suites', 'none', 'all'): From 3570798c103a2b0844f9f546c9f8ee94354d2d46 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 4 Sep 2014 17:28:41 +0000 Subject: [PATCH 157/214] [python3] BuiltIn.Evaluate: sys.maxint-->maxsize in doc string --HG-- extra : transplant_source : %B0L%F8%D4%F6%C0%8B%0D%05D%FDx%87R%B6n9%A4%F7%AD --- src/robot/libraries/BuiltIn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/robot/libraries/BuiltIn.py b/src/robot/libraries/BuiltIn.py index f8c9bc6155c..718990a889c 100644 --- a/src/robot/libraries/BuiltIn.py +++ b/src/robot/libraries/BuiltIn.py @@ -2385,7 +2385,7 @@ def evaluate(self, expression, modules=None, namespace=None): | ${status} = | Evaluate | 0 < ${result} < 10 | | ${down} = | Evaluate | int(${result}) | | ${up} = | Evaluate | math.ceil(${result}) | math | - | ${random} = | Evaluate | random.randint(0, sys.maxint) | random,sys | + | ${random} = | Evaluate | random.randint(0, sys.maxsize) | random,sys | | ${ns} = | Create Dictionary | x=${4} | y=${2} | | ${result} = | Evaluate | x*10 + y | namespace=${ns} | => From 7bc66e77916937ede26cf047cc54f6ab4b3bc532 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 5 Sep 2014 21:29:52 +0000 Subject: [PATCH 158/214] [python3] DateTime: use six.integer_/string_types --HG-- extra : transplant_source : hHf%5Cs%1E%EC%B6%96%A7%CDiY%F8%7B%81%0C%3E%F7%B9 --- src/robot/libraries/DateTime.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/robot/libraries/DateTime.py b/src/robot/libraries/DateTime.py index 7960b8e16b6..f472323034e 100644 --- a/src/robot/libraries/DateTime.py +++ b/src/robot/libraries/DateTime.py @@ -281,6 +281,7 @@ | interval = Time(interval).convert('number') | # ... """ +from six import integer_types, string_types from datetime import datetime, timedelta import time @@ -498,11 +499,11 @@ def __init__(self, date, input_format=None): self.seconds = self._convert_date_to_seconds(date, input_format) def _convert_date_to_seconds(self, date, input_format): - if isinstance(date, basestring): + if isinstance(date, string_types): return self._string_to_epoch(date, input_format) elif isinstance(date, datetime): return self._mktime_with_millis(date) - elif isinstance(date, (int, long, float)): + elif isinstance(date, integer_types + (float,)): return float(date) raise ValueError("Unsupported input '%s'." % date) From 054f7f75f88aab9f971e939ec415bfbcc6846c06 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sat, 6 Sep 2014 10:23:22 +0000 Subject: [PATCH 159/214] [python3] DateTime: make round() calls explicitly return float if intended --HG-- extra : transplant_source : %A30%8F%F0%25%FF%B9%A5%5C%E1%9E%F8E%1CP%23f%9B%96%17 --- src/robot/libraries/DateTime.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/robot/libraries/DateTime.py b/src/robot/libraries/DateTime.py index f472323034e..29e5c03dd5d 100644 --- a/src/robot/libraries/DateTime.py +++ b/src/robot/libraries/DateTime.py @@ -550,7 +550,8 @@ def _mktime_with_millis(self, dt): return time.mktime(dt.timetuple()) + dt.microsecond / 10.0**6 def convert(self, format, millis=True): - seconds = self.seconds if millis else round(self.seconds) + #PY3: round() needs explicit ndigits arg to return float + seconds = self.seconds if millis else round(self.seconds, 0) if '%' in format: return self._convert_to_custom_timestamp(seconds, format) try: @@ -619,7 +620,8 @@ def convert(self, format, millis=True): result_converter = getattr(self, '_convert_to_%s' % format.lower()) except AttributeError: raise ValueError("Unknown format '%s'." % format) - seconds = self.seconds if millis else round(self.seconds) + #PY3: round() needs explicit ndigits arg to return float + seconds = self.seconds if millis else round(self.seconds, 0) return result_converter(seconds, millis) def _convert_to_number(self, seconds, millis=True): From 821c4221cfa4c2b5a028b50eb18b29af20dcdaf2 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sat, 6 Sep 2014 11:01:43 +0000 Subject: [PATCH 160/214] [python3] Create Binary File: use bytearray instead of ''.join(chr(... --HG-- extra : transplant_source : %9ApA%04%B8%16%13%8D%11%903NI%091%93%F5%9F%15%C5 --- src/robot/libraries/OperatingSystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/robot/libraries/OperatingSystem.py b/src/robot/libraries/OperatingSystem.py index 31e2a70f6f4..5f64dc0f981 100644 --- a/src/robot/libraries/OperatingSystem.py +++ b/src/robot/libraries/OperatingSystem.py @@ -656,7 +656,7 @@ def create_binary_file(self, path, content): New in Robot Framework 2.8.5. """ if isinstance(content, unicode): - content = ''.join(chr(ord(c)) for c in content) + content = bytearray(map(ord, content)) path = self._write_to_file(path, content) self._link("Created binary file '%s'", path) From b71d930dc54ca67664ca8bbd9f40c520363f6235 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sat, 6 Sep 2014 11:05:28 +0000 Subject: [PATCH 161/214] [python3] ProcessConfig.full_config: fixed merge mistake: * universal_newlines must be NOT binary_mode --HG-- extra : transplant_source : %D9%9F%EB%E0%9B%A9%BE%AA%C1%9B%FB%C6%7E%B6%C6C%10%F2%95f --- src/robot/libraries/Process.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/robot/libraries/Process.py b/src/robot/libraries/Process.py index 32a9ad04e0e..e74c3026519 100644 --- a/src/robot/libraries/Process.py +++ b/src/robot/libraries/Process.py @@ -863,7 +863,7 @@ def full_config(self): 'shell': self.shell, 'cwd': self.cwd, 'env': self.env, - 'universal_newlines': self.binary_mode} + 'universal_newlines': not self.binary_mode} if hasattr(os, 'setsid') and not sys.platform.startswith('java'): config['preexec_fn'] = os.setsid if hasattr(subprocess, 'CREATE_NEW_PROCESS_GROUP'): From cc84f67d2b456e64dede6ee0bbe2e5b081207d94 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sat, 6 Sep 2014 11:44:51 +0000 Subject: [PATCH 162/214] [python3] atest: process/newlines_and_encoding: no .decode() of os.getenv() in PY3 --HG-- extra : transplant_source : 8%D1%B6-%E8%E3q%E9%2CsD%BF%90d%1D%FD%01%CE%89%7B --- .../standard_libraries/process/newlines_and_encoding.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/atest/testdata/standard_libraries/process/newlines_and_encoding.txt b/atest/testdata/standard_libraries/process/newlines_and_encoding.txt index 2e5991fd36b..2e3f89881fa 100644 --- a/atest/testdata/standard_libraries/process/newlines_and_encoding.txt +++ b/atest/testdata/standard_libraries/process/newlines_and_encoding.txt @@ -12,8 +12,10 @@ Non-ASCII command and output with custom stream [Teardown] Safe Remove File ${STDOUT} Non-ASCII in environment variables - ${result}= Run Process python -c - ... import os, sys; print(os.getenv('X_X').decode(sys.getfilesystemencoding()) \=\= u'hyv\\xe4') + ${code}= Catenate import six, os, sys; value \= os.getenv('X_X'); + ... print((value if six.PY3 else value.decode(sys.getfilesystemencoding())) + ... \=\= u'hyv\\xe4') + ${result}= Run Process python -c ${code} ... env:X_X=hyvä stderr=STDOUT Result should equal ${result} stdout=True From b5107fb7e71c6fe7dac9e5b4dc0d6e3a688816bf Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sat, 6 Sep 2014 14:37:52 +0000 Subject: [PATCH 163/214] [python3] DocFormatter: targets.iteritems()-->.items() --HG-- extra : transplant_source : iE%AD%3B%05zW%9A%8F%A0%8A%2C%B8%88%EFuK%E0%1DP --- src/robot/libdocpkg/htmlwriter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/robot/libdocpkg/htmlwriter.py b/src/robot/libdocpkg/htmlwriter.py index d64d05a15aa..f1fd00ff981 100644 --- a/src/robot/libdocpkg/htmlwriter.py +++ b/src/robot/libdocpkg/htmlwriter.py @@ -107,7 +107,7 @@ def _yield_header_targets(self, introduction): def _escape_and_encode_targets(self, targets): return NormalizedDict((html_escape(key), self._encode_uri_component(value)) - for key, value in targets.iteritems()) + for key, value in targets.items()) def _encode_uri_component(self, value): # Emulates encodeURIComponent javascript function From c933ba80e63e51659d488e90a2fe5c22ebdc73e9 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sat, 6 Sep 2014 14:39:22 +0000 Subject: [PATCH 164/214] [python3] DocFormatter: use six.moves.urllib_parse.quote --HG-- extra : transplant_source : b%A5%95%19%07%A1%A3%1B%85%A8%3F%B3%B4%5B%90%B0K%AEr%C4 --- src/robot/libdocpkg/htmlwriter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/robot/libdocpkg/htmlwriter.py b/src/robot/libdocpkg/htmlwriter.py index f1fd00ff981..46017542c3b 100644 --- a/src/robot/libdocpkg/htmlwriter.py +++ b/src/robot/libdocpkg/htmlwriter.py @@ -13,7 +13,7 @@ # limitations under the License. import re -import urllib +from six.moves.urllib_parse import quote as urlquote from robot.errors import DataError from robot.htmldata import HtmlFileWriter, ModelWriter, JsonWriter, LIBDOC @@ -111,7 +111,7 @@ def _escape_and_encode_targets(self, targets): def _encode_uri_component(self, value): # Emulates encodeURIComponent javascript function - return urllib.quote(value.encode('UTF-8'), safe="-_.!~*'()") + return urlquote(value.encode('UTF-8'), safe="-_.!~*'()") def html(self, doc, intro=False): doc = self._doc_to_html(doc) From 53944eb03a4ed6e0a0bf8af56413bb3302e6d52c Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sat, 6 Sep 2014 14:57:06 +0000 Subject: [PATCH 165/214] [python3] RestReader: use only BytesIO in PY3 --HG-- extra : transplant_source : %BE%F01%F9%A8%FC%BC%E1%1Dx%F8%A4%10%7D%1E%29%04%14%D65 --- src/robot/parsing/restreader.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/robot/parsing/restreader.py b/src/robot/parsing/restreader.py index c5b87b71c52..9b32ce3d804 100644 --- a/src/robot/parsing/restreader.py +++ b/src/robot/parsing/restreader.py @@ -12,13 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from six import PY2, PY3 +from six import PY3 -import sys if PY3: - from io import BytesIO, StringIO + from io import BytesIO else: - from cStringIO import StringIO + from cStringIO import StringIO as BytesIO from .htmlreader import HtmlReader from .txtreader import TxtReader @@ -40,13 +39,12 @@ def read(self, rstfile, rawdata): return self._read_html(doctree, rawdata) def _read_text(self, data, rawdata): - if PY2: - data = data.encode('UTF-8') - txtfile = StringIO(data) + data = data.encode('UTF-8') + txtfile = BytesIO(data) return TxtReader().read(txtfile, rawdata) def _read_html(self, doctree, rawdata): - htmlfile = BytesIO() if PY3 else StringIO() + htmlfile = BytesIO() htmlfile.write(publish_from_doctree( doctree, writer_name='html', settings_overrides={'output_encoding': 'UTF-8'})) From e4a9bb8f394f2df28540e40847f1bea664f2cb51 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 7 Sep 2014 20:51:18 +0000 Subject: [PATCH 166/214] [python3] run_atests: fixed L suffix removal --HG-- extra : transplant_source : A%94%05q%C3%CEo%28%BCcZ%28%16%F9x%0C%C8-%7D%B3 --- atest/run_atests.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/atest/run_atests.py b/atest/run_atests.py index 93accf54dde..88727810354 100644 --- a/atest/run_atests.py +++ b/atest/run_atests.py @@ -35,7 +35,7 @@ sys.exit('Running this script requires Python 2.6 or newer.') # Check for new working dir after 2to3: -if not 'CURDIR' in globals(): +if 'CURDIR' not in globals(): # ==> still the original script before 2to3. CURDIR = dirname(abspath(__file__)) ROBOTDIR = join(CURDIR, '..', 'src', 'robot') @@ -46,7 +46,7 @@ # - Exec this file's copy in-place for actual testing # Is this script already the Python 3 copy? -if not 'do2to3' in globals(): +if 'do2to3' not in globals(): # ==> still the original. do2to3 = True if PY3 and do2to3: @@ -81,7 +81,7 @@ # Replace hex codes in strings # with actual unicode characters, # if not used to create bytes objects: - if not 'remote' in dirpath: + if 'remote' not in dirpath: text = re.sub( r'\\\\x([0-9a-f]{2})', lambda match: ( @@ -94,7 +94,8 @@ lambda match: chr(int(match.group(1), 16)), text) # Remove L suffixes from integer literals: - text = re.sub(r'([1-9][0-9]+)L', r'\1', text) + text = re.sub(r'([1-9][0-9]+)L([^0-9A-Za-z_])', + r'\1\2', text) with open(path, 'w', encoding='utf8') as f: f.write(text) From 145807e4d3d0ed98c857f1f72da6ed0746fe8ba2 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 7 Sep 2014 20:58:37 +0000 Subject: [PATCH 167/214] [python3] XML.save_xml(): fixed merge mistake in tree.write() --HG-- extra : transplant_source : 2%09%B4%88%87%CAH%FEy%04v%7El3%8A%F9%D7%80%25N --- src/robot/libraries/XML.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/robot/libraries/XML.py b/src/robot/libraries/XML.py index 77a0c3575d0..c59ca9bf7ca 100644 --- a/src/robot/libraries/XML.py +++ b/src/robot/libraries/XML.py @@ -1208,7 +1208,7 @@ def save_xml(self, source, path, encoding='UTF-8'): # Opening in binary mode is important for Python 3, # because the ElementTree writes encoded bytes. with open(path, 'wb') as output: - tree.write(output, encoding, **kwargs) + tree.write(output, encoding=encoding, **xml_declaration) def evaluate_xpath(self, source, expression, context='.'): """Evaluates the given xpath expression and returns results. From 64f45b79f55c2e0ee4cb19efa7f67083034b086a Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Sun, 7 Sep 2014 21:27:09 +0000 Subject: [PATCH 168/214] atest: xml: FAIL REGEXP for some expected IOErrors to match derived Errors in PY3 --HG-- extra : transplant_source : %23Pg%3F%22H%AD%B6_%FF%CA%92c%FBK%DA%3E%2A%0C%E4 --- atest/testdata/standard_libraries/xml/parsing_with_lxml.txt | 2 +- atest/testdata/standard_libraries/xml/save_xml_with_lxml.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/atest/testdata/standard_libraries/xml/parsing_with_lxml.txt b/atest/testdata/standard_libraries/xml/parsing_with_lxml.txt index dab2326d1dd..b2d5b2e0daa 100644 --- a/atest/testdata/standard_libraries/xml/parsing_with_lxml.txt +++ b/atest/testdata/standard_libraries/xml/parsing_with_lxml.txt @@ -26,5 +26,5 @@ Parse invalid string Parse XML urho Parse non-existing file - [Documentation] FAIL STARTS: IOError: + [Documentation] FAIL REGEXP: (IO|FileNotFound)Error: .* Parse XML non-existing.xml diff --git a/atest/testdata/standard_libraries/xml/save_xml_with_lxml.txt b/atest/testdata/standard_libraries/xml/save_xml_with_lxml.txt index c7ae81a02b5..b9668d5826a 100644 --- a/atest/testdata/standard_libraries/xml/save_xml_with_lxml.txt +++ b/atest/testdata/standard_libraries/xml/save_xml_with_lxml.txt @@ -38,7 +38,7 @@ Save Non-ASCII XML Using Custom Encoding XML Content Should Be ${NON-ASCII} iso-8859-1 Save to Invalid File - [Documentation] FAIL STARTS: IOError: + [Documentation] FAIL REGEXP: (IO|IsADirectory)Error: .* Save XML ${SIMPLE} %{TEMPDIR} Save Using Invalid Encoding From 365b33f7fc4b10c2f27f2bb2513fd3e04666c009 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 15 Sep 2014 22:27:50 +0000 Subject: [PATCH 169/214] [python3] run_atests: x-exclude-on-py3 --HG-- extra : transplant_source : %81%29%B2Q%2B%B83%A8%91%CA7%C1%5EZ%F1%BFn%FFaf --- atest/run_atests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/atest/run_atests.py b/atest/run_atests.py index 88727810354..b9baa3238a0 100644 --- a/atest/run_atests.py +++ b/atest/run_atests.py @@ -164,6 +164,8 @@ def atests(interpreter_path, *params): args += ' --exclude x-exclude-on-windows' if sys.platform == 'darwin' and 'python' in interpreter: args += ' --exclude x-exclude-on-osx-python' + if PY3: + args += ' --exclude x-exclude-on-py3' if 'ipy' in interpreter: args += ' --noncritical x-fails-on-ipy' command = '%s %s %s %s' % (sys.executable, RUNNER, args, ' '.join(params)) From 4e1512752817f6516907945d42c12b705f472037 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 15 Sep 2014 22:28:55 +0000 Subject: [PATCH 170/214] [python3] atest: builtin/converter: x-exclude-on-py3 Numeric conversions with long types --HG-- extra : transplant_source : %3B%B4b%B1%E0%10%CE%C3%D3-%40%86%CA%D1r%A9%CEY%D6%B7 --- atest/robot/standard_libraries/builtin/converter.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/atest/robot/standard_libraries/builtin/converter.txt b/atest/robot/standard_libraries/builtin/converter.txt index ac296215fbb..746e03ac563 100644 --- a/atest/robot/standard_libraries/builtin/converter.txt +++ b/atest/robot/standard_libraries/builtin/converter.txt @@ -55,6 +55,7 @@ Convert To Number With Precision Check Test Case ${TEST NAME} Numeric conversions with long types + [Tags] jybot pybot x-exclude-on-py3 Check Test Case ${TEST NAME} Convert To String From d017a126506293724c8ca330b49d7a471b35fcd4 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 1 Oct 2014 10:30:27 +0000 Subject: [PATCH 171/214] [python3] setup: read from README.rst instead of .txt --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 28b0597bc4c..a303ac985fb 100755 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ with open(join(dirname(__file__), 'src', 'robot', 'version.py')) as py: exec(py.read()) -README = open(join(dirname(__file__), 'README.txt')).read() +README = open(join(dirname(__file__), 'README.rst')).read() # Maximum width in Windows installer seems to be 70 characters -------| DESCRIPTION = re.match( r"(.|\n)*Robot Framework\n" From 7421f433f4225ae29d7dbbdf64afcd686837003e Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 1 Oct 2014 10:32:36 +0000 Subject: [PATCH 172/214] [python3] encode_to_system(): always do unicode check/conversion --- src/robot/utils/encoding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/robot/utils/encoding.py b/src/robot/utils/encoding.py index 400b0601be4..4fd1a72d8a6 100644 --- a/src/robot/utils/encoding.py +++ b/src/robot/utils/encoding.py @@ -60,8 +60,8 @@ def encode_to_system(string, errors='replace'): Non-Unicode strings are first converted to Unicode. """ - if PY3: - return string if not isinstance(string, unicode): string = unicode(string) + if PY3: + return string return string.encode(SYSTEM_ENCODING, errors) From ce31706839a0634a77af94ee2eea1a470f2c0be0 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 1 Oct 2014 10:33:24 +0000 Subject: [PATCH 173/214] [python3] run_atests: also remove u prefix after } --- atest/run_atests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atest/run_atests.py b/atest/run_atests.py index f381427cac8..f553c2de705 100644 --- a/atest/run_atests.py +++ b/atest/run_atests.py @@ -77,7 +77,7 @@ else: print("Preparing for Python 3: %s" % path) # Remove u prefixes from unicode literals: - text = re.sub(r'([\[(= ])u\'', r'\1\'', text) + text = re.sub(r'([\[(=} ])u\'', r'\1\'', text) # Replace hex codes in strings # with actual unicode characters, # if not used to create bytes objects: From cf8d9ac9f5403cd2935309f2e2536a8cdd78d944 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 24 Feb 2015 16:44:31 +0000 Subject: [PATCH 174/214] [python3] run_atests: copy robot.bmp to atest/python3/ --- atest/run_atests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/atest/run_atests.py b/atest/run_atests.py index f553c2de705..efbe20dc075 100644 --- a/atest/run_atests.py +++ b/atest/run_atests.py @@ -55,6 +55,7 @@ shutil.rmtree((PY3DIR), ignore_errors=True) os.makedirs(join(PY3DIR, 'src')) + shutil.copy(join(CURDIR, '..', 'robot.bmp'), PY3DIR) # needed for 1 test shutil.copytree(ROBOTDIR, join(PY3DIR, 'src', 'robot'), symlinks=True) shutil.copytree( CURDIR, join(PY3ATESTDIR), symlinks=True, From 2f22da7fc4b1b61a25a78b02ce503dc042aaf46b Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 24 Feb 2015 19:01:27 +0000 Subject: [PATCH 175/214] [python3] added and fixed some __nonzero__ --> __bool__ wrappers --- src/robot/libraries/Screenshot.py | 1 + src/robot/model/criticality.py | 1 + src/robot/model/filter.py | 1 + src/robot/model/stats.py | 1 + src/robot/output/librarylisteners.py | 6 +++++- src/robot/running/status.py | 4 ++-- 6 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/robot/libraries/Screenshot.py b/src/robot/libraries/Screenshot.py index 698b2a50040..e4f42cfd127 100644 --- a/src/robot/libraries/Screenshot.py +++ b/src/robot/libraries/Screenshot.py @@ -235,6 +235,7 @@ def __call__(self, path): def __bool__(self): return self.module != 'no' + #PY2 def __nonzero__(self): return self.__bool__() diff --git a/src/robot/model/criticality.py b/src/robot/model/criticality.py index 3f77573c21f..0cfeeccefff 100644 --- a/src/robot/model/criticality.py +++ b/src/robot/model/criticality.py @@ -38,5 +38,6 @@ def test_is_critical(self, test): def __bool__(self): return bool(self.critical_tags or self.non_critical_tags) + #PY2 def __nonzero__(self): return self.__bool__() diff --git a/src/robot/model/filter.py b/src/robot/model/filter.py index 543517e3aa9..c671180fa0a 100644 --- a/src/robot/model/filter.py +++ b/src/robot/model/filter.py @@ -99,5 +99,6 @@ def __bool__(self): return bool(self.include_suites or self.include_tests or self.include_tags or self.exclude_tags) + #PY2 def __nonzero__(self): return self.__bool__() diff --git a/src/robot/model/stats.py b/src/robot/model/stats.py index c41bca653cb..4fd7c376e7f 100644 --- a/src/robot/model/stats.py +++ b/src/robot/model/stats.py @@ -93,6 +93,7 @@ def __lt__(self, other): def __bool__(self): return not self.failed + #PY2 def __nonzero__(self): return self.__bool__() diff --git a/src/robot/output/librarylisteners.py b/src/robot/output/librarylisteners.py index 512cdd659d5..ef7e6a1b151 100644 --- a/src/robot/output/librarylisteners.py +++ b/src/robot/output/librarylisteners.py @@ -23,9 +23,13 @@ def __init__(self): self._setup_or_teardown_type = None self._global_listeners = {} - def __nonzero__(self): + def __bool__(self): return True + #PY2 + def __nonzero__(self): + return self.__bool__() + def _notify_end_test(self, listener, test): Listeners._notify_end_test(self, listener, test) if listener.library_scope == 'TESTCASE': diff --git a/src/robot/running/status.py b/src/robot/running/status.py index 31577985ae3..8d288fa4481 100644 --- a/src/robot/running/status.py +++ b/src/robot/running/status.py @@ -29,7 +29,7 @@ def __bool__(self): #PY2 def __nonzero__(self): - return self.__bool__(self) + return self.__bool__() class Exit(object): @@ -52,7 +52,7 @@ def __bool__(self): #PY2 def __nonzero__(self): - return self.__bool__(self) + return self.__bool__() class _ExecutionStatus(object): From 6a3f02352c6e65fa1495aa8ea1bc1dd6a49330c8 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 24 Feb 2015 19:33:52 +0000 Subject: [PATCH 176/214] [python3] atest_resource: Make test non-critical on Python 3 (and IronPython) --- atest/resources/atest_resource.robot | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/atest/resources/atest_resource.robot b/atest/resources/atest_resource.robot index 83f4a7f838a..ff92a13c4df 100644 --- a/atest/resources/atest_resource.robot +++ b/atest/resources/atest_resource.robot @@ -360,3 +360,11 @@ Make test non-critical if Make test non-critical on IronPython # This test isn't 100% safe. Should come up with better. Make test non-critical if os.sep != '/' and 'ipy' in '${INTERPRETER}' + +Make test non-critical on Python 3 + # This test isn't 100% safe. Should come up with better. + Make test non-critical if sys.version_info[0] == 3 + +Make test non-critical on Python 3 and IronPython + # This test isn't 100% safe. Should come up with better. + Make test non-critical if sys.version_info[0] == 3 or os.sep != '/' and 'ipy' in '${INTERPRETER}' From c4e1d49b238854dce577d2ca031d7ed0722e1eab Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 24 Feb 2015 19:39:09 +0000 Subject: [PATCH 177/214] atest: test_libraries/dynamic_library_python: made 2 non-ascii keyword tests non-critical on py3 (work with encoded keyword names) --- atest/robot/test_libraries/dynamic_library_python.robot | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atest/robot/test_libraries/dynamic_library_python.robot b/atest/robot/test_libraries/dynamic_library_python.robot index f6bf6a15b1c..a6e0d2f8065 100644 --- a/atest/robot/test_libraries/dynamic_library_python.robot +++ b/atest/robot/test_libraries/dynamic_library_python.robot @@ -15,10 +15,11 @@ Global Dynamic Library Check Test Case ${TESTNAME} Non-ASCII keyword name works when Unicode + [Setup] Make test non-critical on Python 3 Check Test Case ${TESTNAME} Non-ASCII keyword name works when UTF-8 bytes - [Setup] Make test non-critical on IronPython + [Setup] Make test non-critical on Python 3 and IronPython Check Test Case ${TESTNAME} Non-ASCII keyword name fails when other bytes From 45c2487dfa9fc0aa69591d171a04ea78d7b5deb2 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 24 Feb 2015 21:00:11 +0000 Subject: [PATCH 178/214] [python3] atest: LibUsingPyLogging: use six.PY3 --- atest/testdata/test_libraries/LibUsingPyLogging.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atest/testdata/test_libraries/LibUsingPyLogging.py b/atest/testdata/test_libraries/LibUsingPyLogging.py index bf69a255485..dcd7c76bdb2 100644 --- a/atest/testdata/test_libraries/LibUsingPyLogging.py +++ b/atest/testdata/test_libraries/LibUsingPyLogging.py @@ -1,4 +1,4 @@ -from six import text_type as unicode +from six import PY3, text_type as unicode import logging import time @@ -25,7 +25,7 @@ def __init__(self, msg=''): def __unicode__(self): return self.msg def __str__(self): - if sys.version_info[0] == 3: + if PY3: return self.__unicode__() return unicode(self).encode('UTF-8') def __repr__(self): From 09c44ac23901221a9d6bd0e020ded4a9a5461ddd Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 24 Feb 2015 21:01:55 +0000 Subject: [PATCH 179/214] [python3] atest: Misspelled Extended Variable Child: FAIL --> FAIL REGEXP (different AttributeError message in py2/3) --- atest/testdata/variables/variable_recommendations.robot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atest/testdata/variables/variable_recommendations.robot b/atest/testdata/variables/variable_recommendations.robot index 14e25035d8a..bfd392d4389 100644 --- a/atest/testdata/variables/variable_recommendations.robot +++ b/atest/testdata/variables/variable_recommendations.robot @@ -99,7 +99,7 @@ Misspelled Extended Variable Parent Log ${OBJJ.name} Misspelled Extended Variable Child - [Documentation] FAIL Resolving variable '${OBJ.nmame}' failed: AttributeError: ExampleObject instance has no attribute 'nmame' + [Documentation] FAIL REGEXP: Resolving variable '\\${OBJ.nmame}' failed: AttributeError: ('ExampleObject' object|ExampleObject instance) has no attribute 'nmame' Log ${OBJ.nmame} Existing Non ASCII Variable Name From b944af4867d6f1165d2ec22c02c1e38fae9477a0 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 24 Feb 2015 21:03:27 +0000 Subject: [PATCH 180/214] [python3] utils.unic: except all exceptions for returning _unrepresentable_object in py3 variant --- src/robot/utils/unic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/robot/utils/unic.py b/src/robot/utils/unic.py index 7621b5aeda4..09f6ac3ab83 100644 --- a/src/robot/utils/unic.py +++ b/src/robot/utils/unic.py @@ -76,7 +76,7 @@ def _unic(item, *args): return item.decode('ascii') try: return str(item, *args) - except (UnicodeError, TypeError): + except: return _unrepresentable_object(item) From 683813cf3e2c66905a570b2333caf156982fa4a6 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 24 Feb 2015 21:39:27 +0000 Subject: [PATCH 181/214] [python3] Remote: use six.moves.http_client.HTTPConnection --- src/robot/libraries/Remote.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/robot/libraries/Remote.py b/src/robot/libraries/Remote.py index bffe8f38a11..e435ac1029c 100644 --- a/src/robot/libraries/Remote.py +++ b/src/robot/libraries/Remote.py @@ -14,7 +14,7 @@ from six import PY3, integer_types, string_types -import httplib +from six.moves.http_client import HTTPConnection import re import socket import sys @@ -251,7 +251,7 @@ def make_connection(self, host): if self._connection and host == self._connection[0]: return self._connection[1] chost, self._extra_headers, x509 = self.get_host_info(host) - self._connection = host, httplib.HTTPConnection(chost, timeout=self.timeout) + self._connection = host, HTTPConnection(chost, timeout=self.timeout) return self._connection[1] @@ -263,7 +263,7 @@ def make_connection(self, host): host, extra_headers, x509 = self.get_host_info(host) return TimeoutHTTP(host, timeout=self.timeout) - class TimeoutHTTP(httplib.HTTP): + class TimeoutHTTP(HTTPConnection): def __init__(self, host='', port=None, strict=None, timeout=None): if port == 0: From c042d0898d813d98151979d811aac6e8aff00a33 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 25 Feb 2015 08:37:46 +0000 Subject: [PATCH 182/214] [python3] atest: Collections.List.Sort List: corrected expexted list --- atest/testdata/standard_libraries/collections/list.robot | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atest/testdata/standard_libraries/collections/list.robot b/atest/testdata/standard_libraries/collections/list.robot index 37819650f50..478d76442d0 100644 --- a/atest/testdata/standard_libraries/collections/list.robot +++ b/atest/testdata/standard_libraries/collections/list.robot @@ -144,7 +144,8 @@ Reserve List Sort List Sort List ${STRINGS} - Compare To Expected String ${STRINGS} ['1', '1' , '1', '41', '43', '44'] + Compare To Expected String ${STRINGS} + ... [u'!@#$%^&*()_+-=', u'\${cmd list}', u'1', u'2', u'3', u'B', u'WOrd', u'a', u'b', u'glob=test', u'regexp=blah', u'wOrD', u'äö'] Get From List ${value} = Get From List ${L4} 1 From 8155bdd2043f4a4c33f5c380d798845b03129874 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 25 Feb 2015 08:39:15 +0000 Subject: [PATCH 183/214] [python3] atest: Datetime: modified some added milliseconds to get same rounding results in py2/3 --- .../datetime/convert_date_result_format.robot | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/atest/testdata/standard_libraries/datetime/convert_date_result_format.robot b/atest/testdata/standard_libraries/datetime/convert_date_result_format.robot index 8cf1aee261c..eeea8b6e24d 100644 --- a/atest/testdata/standard_libraries/datetime/convert_date_result_format.robot +++ b/atest/testdata/standard_libraries/datetime/convert_date_result_format.robot @@ -60,14 +60,14 @@ Should exclude milliseconds 2014-04-24 21:45:12.99999 timestamp 2014-04-24 21:45:13 ${DATE} timestamp 2014-04-24 21:45:12 ${EPOCH + 0.123} %Y-%m-%d %H:%M:%S 2014-04-24 21:45:12 - ${EPOCH + 0.500} %Y-%m-%d %H:%M:%S 2014-04-24 21:45:13 + ${EPOCH + 1.500} %Y-%m-%d %H:%M:%S 2014-04-24 21:45:14 ${DATE} datetime ${datetime(2014, 4, 24, 21, 45, 12)} ${DATE w/ MILLIS} datetime ${datetime(2014, 4, 24, 21, 45, 12)} ${DATE w/ MICRO} datetime ${datetime(2014, 4, 24, 21, 45, 12)} ${EPOCH + 0.123} datetime ${datetime(2014, 4, 24, 21, 45, 12)} - ${EPOCH + 0.500} datetime ${datetime(2014, 4, 24, 21, 45, 13)} + ${EPOCH + 1.500} datetime ${datetime(2014, 4, 24, 21, 45, 14)} ${EPOCH + 0.123} epoch ${EPOCH} - ${EPOCH + 0.500} epoch ${EPOCH + 1} + ${EPOCH + 1.500} epoch ${EPOCH + 2} Epoch time is float regardless are millis included or not [Template] Epoch time format should be From da1a6368ac4f827b490dd08b63dc60097797ceee Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 25 Feb 2015 09:57:17 +0000 Subject: [PATCH 184/214] [python3] setup: use io.open for common newlines --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 88b9cd63b11..0ff2ce092ca 100755 --- a/setup.py +++ b/setup.py @@ -4,6 +4,7 @@ import sys import os from os.path import abspath, join, dirname +from io import open from setuptools import setup if 'develop' in sys.argv or 'bdist_wheel' in sys.argv: From 97af81da66d3a4477263923b1e5926190e04a647 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 25 Feb 2015 22:08:54 +0000 Subject: [PATCH 185/214] [python3] six.add_metaclass-->with_metaclass --- src/robot/model/modelobject.py | 5 ++--- src/robot/output/listeners.py | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/robot/model/modelobject.py b/src/robot/model/modelobject.py index b637a00ce64..bcbc24ce26b 100644 --- a/src/robot/model/modelobject.py +++ b/src/robot/model/modelobject.py @@ -12,15 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -from six import PY3, add_metaclass +from six import PY3, with_metaclass import sys from robot.utils.setter import SetterAwareType -@add_metaclass(SetterAwareType) -class ModelObject(object): +class ModelObject(with_metaclass(SetterAwareType, object)): __slots__ = [] def __unicode__(self): diff --git a/src/robot/output/listeners.py b/src/robot/output/listeners.py index 371c67f11b1..49ae6dce702 100644 --- a/src/robot/output/listeners.py +++ b/src/robot/output/listeners.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from six import add_metaclass, text_type as unicode +from six import with_metaclass, text_type as unicode import inspect import os.path @@ -53,8 +53,7 @@ def wrapped(self, *args): return wrapped -@add_metaclass(_RecursionAvoidingMetaclass) -class Listeners(object): +class Listeners(with_metaclass(_RecursionAvoidingMetaclass, object)): _start_attrs = ('id', 'doc', 'starttime', 'longname') _end_attrs = _start_attrs + ('endtime', 'elapsedtime', 'status', 'message') _kw_extra_attrs = ('args', '-id', '-longname', '-message') From 2be352f9735f085c0dfad781f66e53249f59e694 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 25 Feb 2015 22:22:25 +0000 Subject: [PATCH 186/214] [python3] api.logger: integer div --- src/robot/api/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/robot/api/logger.py b/src/robot/api/logger.py index d949fc79ecb..efd59129b53 100644 --- a/src/robot/api/logger.py +++ b/src/robot/api/logger.py @@ -78,7 +78,7 @@ def write(msg, level, html=False): librarylogger.write(msg, level, html) else: logger = logging.getLogger("RobotFramework") - level = {'TRACE': logging.DEBUG/2, + level = {'TRACE': logging.DEBUG//2, 'DEBUG': logging.DEBUG, 'INFO': logging.INFO, 'HTML': logging.INFO, From d2cbb69f0a008f7c2bfa43a141671ead2e409dcc Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 25 Feb 2015 22:23:29 +0000 Subject: [PATCH 187/214] [python3] utest: test_frange: compatible range() use --- utest/utils/test_frange.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/utest/utils/test_frange.py b/utest/utils/test_frange.py index 6269490281c..096c1a25bda 100644 --- a/utest/utils/test_frange.py +++ b/utest/utils/test_frange.py @@ -1,3 +1,5 @@ +from six.moves import range + import unittest from robot.utils.frange import frange, _digits @@ -25,8 +27,8 @@ def test_numbers_with_e(self): def test_compatibility_with_range(self): for input in [(10,), (-10,), (1, 10), (1, 10, 2), (10, -5, -2)]: - assert_equals(frange(*input), range(*input)) - assert_equals(frange(*(float(i) for i in input)), range(*input)) + assert_equals(frange(*input), list(range(*input))) + assert_equals(frange(*(float(i) for i in input)), list(range(*input))) def test_preserve_type(self): for input in [(2,), (0, 2), (0, 2, 1)]: From cf9af1670f32708c6e202d401b3c58bc532c6813 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 26 Feb 2015 00:37:42 +0000 Subject: [PATCH 188/214] [python3] setup: updated url --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 0ff2ce092ca..760c466c8c4 100755 --- a/setup.py +++ b/setup.py @@ -77,8 +77,8 @@ author_email = 'robotframework@gmail.com', maintainer = 'Stefan Zimmermann', maintainer_email = 'zimmermann.code@gmail.com', - url = 'https://bitbucket.org/userzimmermann' - '/robotframework-python3', + url = 'https://github.com/userzimmermann' + '/robotframework/tree/python3', download_url = 'https://pypi.python.org/pypi/robotframework-python3', license = 'Apache License 2.0', description = 'Python 3 compatible generic test automation framework', From 9bcd32d793af260b7fa1a2910db880b4a569bbdf Mon Sep 17 00:00:00 2001 From: HelioGuilherme66 Date: Sun, 1 Mar 2015 01:30:21 +0000 Subject: [PATCH 189/214] Fixes path to run unit tests in both python 2.7 and 3.3. --- atest/robot/external/unit_tests.robot | 2 +- atest/run_atests.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/atest/robot/external/unit_tests.robot b/atest/robot/external/unit_tests.robot index 3e33756227d..750fde29d27 100644 --- a/atest/robot/external/unit_tests.robot +++ b/atest/robot/external/unit_tests.robot @@ -5,7 +5,7 @@ Force Tags smoke regression Resource atest_resource.robot *** Variables *** -${TESTPATH} ${CURDIR}${/}..${/}..${/}..${/}utest${/}run_utests.py +${TESTPATH} ${CURDIR}${/}${PY3REL}..${/}..${/}..${/}utest${/}run_utests.py *** Test Cases *** Unit Tests With Python diff --git a/atest/run_atests.py b/atest/run_atests.py index efbe20dc075..9e0b7fc955c 100644 --- a/atest/run_atests.py +++ b/atest/run_atests.py @@ -167,6 +167,9 @@ def atests(interpreter_path, *params): args += ' --exclude x-exclude-on-osx-python' if PY3: args += ' --exclude x-exclude-on-py3' + args += ' --variable PY3REL:../../' # required in unit_tests.robot + else: + args += ' --variable PY3REL:' if 'ipy' in interpreter: args += ' --noncritical x-fails-on-ipy' command = '%s %s %s %s' % (sys.executable, RUNNER, args, ' '.join(params)) From 1dcc60c8795f99d0e36ae7262a3efe88d1176cfc Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 20 Apr 2015 19:15:16 +0200 Subject: [PATCH 190/214] [python3] fixed new utest FAILs --- utest/utils/test_dotdict.py | 14 ++++++++------ utest/utils/test_robottypes.py | 3 ++- utest/utils/test_unic.py | 21 +++++++++++++++------ 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/utest/utils/test_dotdict.py b/utest/utils/test_dotdict.py index d1393fafb53..473b948fdc9 100644 --- a/utest/utils/test_dotdict.py +++ b/utest/utils/test_dotdict.py @@ -45,18 +45,20 @@ def test_is_ordered(self): self.dd.z = 'new value' self.dd.a_new_item = 'last' self.dd.pop('x') - assert_equal(self.dd.items(), [('z', 'new value'), (2, 'y'), - ('a_new_item', 'last')]) + assert_equal(list(self.dd.items()), + [('z', 'new value'), (2, 'y'), + ('a_new_item', 'last')]) self.dd.x = 'last' - assert_equal(self.dd.items(), [('z', 'new value'), (2, 'y'), - ('a_new_item', 'last'), ('x', 'last')]) + assert_equal(list(self.dd.items()), + [('z', 'new value'), (2, 'y'), + ('a_new_item', 'last'), ('x', 'last')]) def test_order_does_not_affect_equality(self): d = dict(a=1, b=2, c=3, d=4, e=5, f=6, g=7) od1 = OrderedDict(sorted(d.items())) - od2 = OrderedDict(reversed(od1.items())) + od2 = OrderedDict(reversed(list(od1.items()))) dd1 = DotDict(sorted(d.items())) - dd2 = DotDict(reversed(dd1.items())) + dd2 = DotDict(reversed(list(dd1.items()))) for d1, d2 in [(dd1, dd2), (dd1, d), (dd2, d), (dd1, od1), (dd2, od2)]: assert_equal(d1, d2) assert_equal(d2, d1) diff --git a/utest/utils/test_robottypes.py b/utest/utils/test_robottypes.py index b9435d32818..4a28589e19c 100644 --- a/utest/utils/test_robottypes.py +++ b/utest/utils/test_robottypes.py @@ -122,7 +122,8 @@ def test_custom_objects(self): class NewStyle(object): pass class OldStyle: pass for item, exp in [(NewStyle(), 'NewStyle'), (OldStyle(), 'OldStyle'), - (NewStyle, 'type'), (OldStyle, 'classobj')]: + (NewStyle, 'type'), + (OldStyle, 'classobj' if PY2 else 'type')]: assert_equals(type_name(item), exp) if JYTHON: diff --git a/utest/utils/test_unic.py b/utest/utils/test_unic.py index 8cf5941ed71..21b4928c166 100644 --- a/utest/utils/test_unic.py +++ b/utest/utils/test_unic.py @@ -1,4 +1,4 @@ -from six import PY3 +from six import PY2, PY3 import unittest import re @@ -118,11 +118,11 @@ def _verify(self, item, expected=None): def test_no_u_prefix(self): self._verify(u'foo', "'foo'") self._verify(u"f'o'o", "\"f'o'o\"") - self._verify(u'hyv\xe4', "'hyv\\xe4'") + self._verify(u'hyv\xe4', "'hyv\\xe4'" if PY2 else None) def test_b_prefix(self): self._verify('foo', "b'foo'") - self._verify('hyv\xe4', "b'hyv\\xe4'") + self._verify('hyv\xe4', "b'hyv\\xe4'" if PY2 else None) def test_non_strings(self): for inp in [1, -2.0, True, None, -2.0, (), [], {}, @@ -157,11 +157,20 @@ def test_collections(self): inp1, inp2 = ReprFails(), StrFails() exp1, exp2 = inp1.unrepr, repr(inp2) self._verify((inp1, inp2, [inp1]), - '(%s, %s, [%s])' % (exp1, exp2, exp1)) + '(%s, %s, [%s])' % (exp1, exp2, exp1) + #PY3: different pprint.PrettyPrinter behavior + # - doesn't iterate container + # if PrettyPrinter.format() text of container + # is not longer than max line width + # - see PrettyPrinter._format(): ... if sepLines: ... + if PY2 else UnRepr.format('tuple', UnRepr.error)) self._verify({'x': 1, 2: u'y'}, "{2: 'y', b'x': 1}") self._verify({1: inp1, None: ()}, - '{None: (), 1: %s}' % exp1) + '{None: (), 1: %s}' % exp1 + #PY3: different pprint.PrettyPrinter behavior + # - see above + if PY2 else UnRepr.format('dict', UnRepr.error)) def test_dotdict(self): self._verify(DotDict({'x': 1, 2: u'y'}), @@ -177,7 +186,7 @@ def test_split_big_collections(self): self._verify(range(100)) self._verify([u'Hello, world!'] * 10, '[%s]' % ', '.join(["'Hello, world!'"] * 10)) - self._verify(range(300), + self._verify(list(range(300)), '[%s]' % ',\n '.join(str(i) for i in range(300))) self._verify([u'Hello, world!'] * 30, '[%s]' % ',\n '.join(["'Hello, world!'"] * 30)) From 4ee830f9ceece9debc8d8e114532de9903fe862b Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 21 Apr 2015 08:21:48 +0200 Subject: [PATCH 191/214] [python3] running.testlibraries: use six.text_type as unicode --- src/robot/running/testlibraries.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/robot/running/testlibraries.py b/src/robot/running/testlibraries.py index 8cbcbc0edf7..00d4a6a5c30 100644 --- a/src/robot/running/testlibraries.py +++ b/src/robot/running/testlibraries.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import text_type as unicode + import inspect import os From 0b290008ff9e0dbbea21104bf156dce5fca3b316 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 21 Apr 2015 08:23:08 +0200 Subject: [PATCH 192/214] [python3] run_atests: changed range for converting \x?? codes in string literals --- atest/run_atests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atest/run_atests.py b/atest/run_atests.py index 9e0b7fc955c..0496d07e4ae 100644 --- a/atest/run_atests.py +++ b/atest/run_atests.py @@ -82,12 +82,12 @@ # Replace hex codes in strings # with actual unicode characters, # if not used to create bytes objects: - if 'remote' not in dirpath: + if not dirpath.endswith('remote'): text = re.sub( r'\\\\x([0-9a-f]{2})', lambda match: ( chr(int(match.group(1), 16)) - if match.group(1) >= '80' + if '80' <= match.group(1) < 'ff' else match.group(0)), text) text = re.sub( From cd469ae31028c4280f2aea4b555a9656d262429f Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 21 Apr 2015 08:23:51 +0200 Subject: [PATCH 193/214] [python3] fixed some new atest FAILs --- .../test_libraries/dynamic_library_python.robot | 2 +- .../keywords/resources/embedded_args_in_lk_1.py | 12 +++++++----- .../keywords/resources/embedded_args_in_lk_2.py | 2 +- .../standard_libraries/builtin/log.robot | 10 +++++----- .../test_libraries/InitImportingAndIniting.py | 6 ++++-- .../dict_variable_in_variable_table.robot | 16 ++++++++++------ .../list_and_dict_from_variable_file.robot | 3 ++- atest/testresources/testlibs/ExampleLibrary.py | 2 ++ 8 files changed, 32 insertions(+), 21 deletions(-) diff --git a/atest/robot/test_libraries/dynamic_library_python.robot b/atest/robot/test_libraries/dynamic_library_python.robot index 99ea48d5b51..6bf3a37ed74 100644 --- a/atest/robot/test_libraries/dynamic_library_python.robot +++ b/atest/robot/test_libraries/dynamic_library_python.robot @@ -23,7 +23,7 @@ Non-ASCII keyword name works when UTF-8 bytes Check Test Case ${TESTNAME} Non-ASCII keyword name fails when other bytes - [Setup] Make test non-critical on IronPython + [Setup] Make test non-critical on Python 3 and IronPython Check Test Case ${TESTNAME} Run Keyword in Static Library diff --git a/atest/testdata/keywords/resources/embedded_args_in_lk_1.py b/atest/testdata/keywords/resources/embedded_args_in_lk_1.py index 67e81753166..bb6c561eff2 100755 --- a/atest/testdata/keywords/resources/embedded_args_in_lk_1.py +++ b/atest/testdata/keywords/resources/embedded_args_in_lk_1.py @@ -1,14 +1,16 @@ +from __future__ import print_function + from robot.api.deco import keyword from robot.libraries.BuiltIn import BuiltIn @keyword(name="User ${user} Selects ${item} From Webshop") def user_selects_from_webshop(user, item): - print "This is always executed" + print("This is always executed") return user, item @keyword(name="${prefix:Given|When|Then} this \"${item}\" ${no good name for this arg ...}") def this(ignored_prefix, item, somearg): - print "%s-%s" % (item, somearg) + print("%s-%s" % (item, somearg)) @keyword(name="My embedded ${var}") def my_embedded(var): @@ -20,15 +22,15 @@ def gets_from_the(x, y, z): @keyword(name="${a}-lib-${b}") def mult_match1(a, b): - print "%s-lib-%s" % (a, b) + print("%s-lib-%s" % (a, b)) @keyword(name="${a}+lib+${b}") def mult_match2(a, b): - print "%s+lib+%s" % (a, b) + print("%s+lib+%s" % (a, b)) @keyword(name="${a}*lib*${b}") def mult_match3(a, b): - print "%s*lib*%s" % (a, b) + print("%s*lib*%s" % (a, b)) @keyword(name="I execute \"${x:[^\"]*}\"") def i_execute(x): diff --git a/atest/testdata/keywords/resources/embedded_args_in_lk_2.py b/atest/testdata/keywords/resources/embedded_args_in_lk_2.py index 80d24ec9299..7f529acdd60 100755 --- a/atest/testdata/keywords/resources/embedded_args_in_lk_2.py +++ b/atest/testdata/keywords/resources/embedded_args_in_lk_2.py @@ -3,4 +3,4 @@ @keyword(name="${a}*lib*${b}") def mult_match3(a, b): - print "%s*lib*%s" % (a, b) + print("%s*lib*%s" % (a, b)) diff --git a/atest/testdata/standard_libraries/builtin/log.robot b/atest/testdata/standard_libraries/builtin/log.robot index 56c06b03a20..de28bff6e2e 100644 --- a/atest/testdata/standard_libraries/builtin/log.robot +++ b/atest/testdata/standard_libraries/builtin/log.robot @@ -49,7 +49,7 @@ Log repr [Setup] Set Log Level DEBUG Log Hyvää yötä \u2603! repr=True Log ${42} DEBUG ${FALSE} ${FALSE} ${TRUE} - ${bytes} = Evaluate chr(0) + chr(255) + ${bytes} = Convert To Bytes 0 255 input_type=int Log ${bytes} repr=yes ${list} = Create List Hyvä \u2603 ${42} ${bytes} Log ${list} repr=yes console=please @@ -57,15 +57,15 @@ Log repr Log pprint ${dict} = Evaluate {u'a long string': 1, u'a longer string!': 2, u'a much, much, much, much, much, much longer string': 3, u'list': [u'a long string', u'a longer string!', u'a much, much, much, much, much, much longer string']} Log ${dict} repr=yes console=please - ${list} = Evaluate ['One', u'Two', 3] + ${list} = Evaluate [b'One', u'Two', 3] Log ${list} repr=yes console=please - ${list} = Evaluate ['a long string', u'a longer string!', u'a much, much, much, much, much, much longer string'] + ${list} = Evaluate [b'a long string', u'a longer string!', u'a much, much, much, much, much, much longer string'] Log ${list} repr=yes console=please ${dict} = Evaluate {u'a long string': 1, u'a longer string!': 2, u'a much, much, much, much, much, much longer string': 3, u'list': [u'a long string', u'a longer string!', u'a much, much, much, much, much, much longer string']} Log ${dict} repr=yes - ${list} = Evaluate [u'One', 'Two', 3] + ${list} = Evaluate [u'One', b'Two', 3] Log ${list} repr=yes - ${dict} = Evaluate {u'a long string': 1, u'a longer string!': 2, u'a much, much, much, much, much, much longer string': 3, u'list': [u'a long string', ${42}, u'Hyvää yötä \u2603!', u'a much, much, much, much, much, much longer string', '\\x00\\xff']} + ${dict} = Evaluate {u'a long string': 1, u'a longer string!': 2, u'a much, much, much, much, much, much longer string': 3, u'list': [u'a long string', ${42}, u'Hyvää yötä \u2603!', u'a much, much, much, much, much, much longer string', b'\\x00\\xff']} Log ${dict} repr=yes console=please Log callable diff --git a/atest/testdata/test_libraries/InitImportingAndIniting.py b/atest/testdata/test_libraries/InitImportingAndIniting.py index 324f5060586..16fd190cda2 100644 --- a/atest/testdata/test_libraries/InitImportingAndIniting.py +++ b/atest/testdata/test_libraries/InitImportingAndIniting.py @@ -1,3 +1,5 @@ +from __future__ import print_function + from robot.libraries.BuiltIn import BuiltIn from robot.api import logger @@ -8,7 +10,7 @@ def __init__(self): BuiltIn().import_library('String') def kw_from_lib_with_importing_init(self): - print 'Keyword from library with importing __init__.' + print('Keyword from library with importing __init__.') class Initting(object): @@ -28,4 +30,4 @@ def __init__(self, id): self.id = id def kw_from_lib_initted_by_init(self): - print 'Keyword from library initted by __init__ (id: %s).' % self.id + print('Keyword from library initted by __init__ (id: %s).' % self.id) diff --git a/atest/testdata/variables/dict_variable_in_variable_table.robot b/atest/testdata/variables/dict_variable_in_variable_table.robot index 2f4dc1f8791..764b34bbe07 100644 --- a/atest/testdata/variables/dict_variable_in_variable_table.robot +++ b/atest/testdata/variables/dict_variable_in_variable_table.robot @@ -76,23 +76,27 @@ Dict from variable table should be ordered 1 @{expected values} = Evaluate [str(i+1) for i in range(21)] ${keys} = Create List @{MANY ITEMS} Should Be Equal ${keys} ${expected keys} - Should Be Equal ${MANY ITEMS.values()} ${expected values} + ${values} = Create List @{MANY ITEMS.values()} + Should Be Equal ${values} ${expected values} Set To Dictionary ${MANY ITEMS} a new value Set To Dictionary ${MANY ITEMS} z new item Append To List ${expected keys} z Set List Value ${expected values} 0 new value Append To List ${expected values} new item - Should Be Equal ${MANY ITEMS.keys()} ${expected keys} - Should Be Equal ${MANY ITEMS.values()} ${expected values} + ${keys} = Create List @{MANY ITEMS.keys()} + Should Be Equal ${keys} ${expected keys} + ${values} = Create List @{MANY ITEMS.values()} + Should Be Equal ${values} ${expected values} Dict from variable table should be ordered 2 [Template] NONE Should Be Equal @{MANY ITEMS}[0] a Should Be Equal @{MANY ITEMS}[1] b Should Be Equal @{MANY ITEMS}[-1] z - Should Be Equal ${MANY ITEMS.values()[0]} new value - Should Be Equal ${MANY ITEMS.values()[1]} 2 - Should Be Equal ${MANY ITEMS.values()[-1]} new item + ${values} = Create List @{MANY ITEMS.values()} + Should Be Equal ${values[0]} new value + Should Be Equal ${values[1]} 2 + Should Be Equal ${values[-1]} new item Dict from variable table should be dot-accessible [Template] NONE diff --git a/atest/testdata/variables/list_and_dict_from_variable_file.robot b/atest/testdata/variables/list_and_dict_from_variable_file.robot index 0aaea499cf3..f2c8ab9ba34 100644 --- a/atest/testdata/variables/list_and_dict_from_variable_file.robot +++ b/atest/testdata/variables/list_and_dict_from_variable_file.robot @@ -31,7 +31,8 @@ Dict is dotted Dict is ordered Should Be Equal @{ORDERED}[0] a Should Be Equal @{ORDERED}[-1] j - Should Be Equal ${ORDERED.keys()} ${EXP KEYS} + ${keys} = Create List @{ORDERED.keys()} + Should Be Equal ${keys} ${EXP KEYS} Invalid list Variable Should Not Exist ${INV LIST} diff --git a/atest/testresources/testlibs/ExampleLibrary.py b/atest/testresources/testlibs/ExampleLibrary.py index aaaf25562e9..c31afc96055 100644 --- a/atest/testresources/testlibs/ExampleLibrary.py +++ b/atest/testresources/testlibs/ExampleLibrary.py @@ -164,6 +164,8 @@ def __unicode__(self): class FailiningUnicode(object): def __init__(self, identifier=identifier): self.identifier = identifier + def __str__(self): + raise ValueError def __unicode__(self): raise ValueError if just_one: From 968f648d78557185ae6f6b8138c09f1b27e92884 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 21 Apr 2015 08:49:11 +0200 Subject: [PATCH 194/214] [python3] requirements: six >= 1.9 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ffe2fce4989..2d3acc1022d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -six +six >= 1.9 From d016da1eb9fafd78c23ff21693700916dcf9e040 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 21 Apr 2015 11:12:24 +0200 Subject: [PATCH 195/214] [python3] fixed utils.unic() override for PY3 --- src/robot/utils/unic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/robot/utils/unic.py b/src/robot/utils/unic.py index 37986337c4d..376dd64c9cb 100644 --- a/src/robot/utils/unic.py +++ b/src/robot/utils/unic.py @@ -34,7 +34,7 @@ def unic(item, *args): return _unrepresentable_object(item) if PY3: - def _unic(item, *args): + def unic(item, *args): if isinstance(item, str): return item if isinstance(item, (bytes, bytearray)) and not args: From 274c2e15225e1fff5ef37b10d8da0d0da3c49490 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Tue, 21 Apr 2015 12:10:36 +0200 Subject: [PATCH 196/214] [python3] more six.reraise --- src/robot/running/timeouts/ironpython.py | 4 +++- src/robot/running/timeouts/jython.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/robot/running/timeouts/ironpython.py b/src/robot/running/timeouts/ironpython.py index 91155c5b5ac..8e19a9013ae 100644 --- a/src/robot/running/timeouts/ironpython.py +++ b/src/robot/running/timeouts/ironpython.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import reraise + import sys import threading @@ -54,4 +56,4 @@ def __call__(self): def get_result(self): if not self._error: return self._result - raise self._error[0], self._error[1], self._error[2] + reraise(self._error[0], self._error[1], self._error[2]) diff --git a/src/robot/running/timeouts/jython.py b/src/robot/running/timeouts/jython.py index 16f3ce1caad..c278f397f47 100644 --- a/src/robot/running/timeouts/jython.py +++ b/src/robot/running/timeouts/jython.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import reraise + import sys from java.lang import Thread, Runnable @@ -53,4 +55,4 @@ def run(self): def get_result(self): if not self._error: return self._result - raise self._error[0], self._error[1], self._error[2] + reraise(self._error[0], self._error[1], self._error[2]) From 1e704b536a78f918cec0c05b6184bbf9941d9101 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 29 May 2015 00:27:44 +0200 Subject: [PATCH 197/214] [python3] running.keywordrunner: use six.integer_types --- src/robot/running/keywordrunner.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/robot/running/keywordrunner.py b/src/robot/running/keywordrunner.py index 9ba071d601c..77079a17005 100644 --- a/src/robot/running/keywordrunner.py +++ b/src/robot/running/keywordrunner.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from six import text_type as unicode +from six import integer_types, text_type as unicode from robot.errors import (ExecutionFailed, ExecutionFailures, ExecutionPassed, ExitForLoop, ContinueForLoop, DataError, @@ -201,10 +201,10 @@ def _get_range_items(self, items): return frange(*items) def _to_number_with_arithmetics(self, item): - if isinstance(item, (int, long, float)): + if isinstance(item, integer_types + (float, )): return item number = eval(str(item), {}) - if not isinstance(number, (int, long, float)): + if not isinstance(number, integer_types + (float, )): raise TypeError("Expected number, got %s." % type_name(item)) return number From d3353e63699f19e693429f88813bff62e2a74542 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 29 May 2015 00:30:40 +0200 Subject: [PATCH 198/214] [python3] VariableStore.resolve_delayed(): always create list of items for loop --- src/robot/variables/store.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/robot/variables/store.py b/src/robot/variables/store.py index f95daa73a2f..e8feda9ba35 100644 --- a/src/robot/variables/store.py +++ b/src/robot/variables/store.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3 + from robot.errors import DataError from robot.utils import (DotDict, is_dict_like, is_list_like, NormalizedDict, type_name) @@ -28,7 +30,10 @@ def __init__(self, variables): self._variables = variables def resolve_delayed(self): - for name, value in self.data.items(): + items = self.data.items() + if PY3: # need list() because items can be removed during loop + items = list(items) + for name, value in items: try: self._resolve_delayed(name, value) except DataError: From 52ff10b4755491da681ff8ff87933f320067d3c8 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 29 May 2015 00:31:29 +0200 Subject: [PATCH 199/214] [python3] test_normalizing: new compatibility fixes --- utest/utils/test_normalizing.py | 40 ++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/utest/utils/test_normalizing.py b/utest/utils/test_normalizing.py index 8ac5c3db0cc..97159530be2 100644 --- a/utest/utils/test_normalizing.py +++ b/utest/utils/test_normalizing.py @@ -1,4 +1,4 @@ -from six import PY3 +from six import PY2, PY3 import unittest if PY3: @@ -116,22 +116,22 @@ def test_contains(self): def test_original_keys_are_preserved(self): nd = NormalizedDict({'low': 1, 'UP': 2}) nd['up'] = nd['Spa Ce'] = 3 - assert_equals(nd.keys(), ['low', 'Spa Ce', 'UP']) - assert_equals(nd.items(), [('low', 1), ('Spa Ce', 3), ('UP', 3)]) + assert_equals(list(nd.keys()), ['low', 'Spa Ce', 'UP']) + assert_equals(list(nd.items()), [('low', 1), ('Spa Ce', 3), ('UP', 3)]) def test_deleting_items(self): nd = NormalizedDict({'A': 1, 'b': 2}) del nd['A'] del nd['B'] assert_equals(nd._data, {}) - assert_equals(nd.keys(), []) + assert_equals(list(nd.keys()), []) def test_pop(self): nd = NormalizedDict({'A': 1, 'b': 2}) assert_equals(nd.pop('A'), 1) assert_equals(nd.pop('B'), 2) assert_equals(nd._data, {}) - assert_equals(nd.keys(), []) + assert_equals(list(nd.keys()), []) def test_pop_with_default(self): assert_equals(NormalizedDict().pop('nonex', 'default'), 'default') @@ -142,7 +142,7 @@ def test_popitem(self): for i in range(9): assert_equals(nd.popitem(), items[i]) assert_equals(nd._data, {}) - assert_equals(nd.keys(), []) + assert_equals(list(nd.keys()), []) def test_popitem_empty(self): assert_raises(KeyError, NormalizedDict().popitem) @@ -216,39 +216,43 @@ def test_iter(self): def test_keys_are_sorted(self): nd = NormalizedDict((c, None) for c in 'aBcDeFg123XyZ___') - assert_equals(nd.keys(), list('123_aBcDeFgXyZ')) + assert_equals(list(nd.keys()), list('123_aBcDeFgXyZ')) def test_iterkeys_and_keys(self): nd = NormalizedDict({'A': 1, 'b': 3, 'C': 2}) - iterator = nd.iterkeys() + iterator = nd.iterkeys() if PY2 else nd.keys() assert_false(isinstance(iterator, list)) assert_equals(list(iterator), ['A', 'b', 'C']) - assert_equals(list(iterator), []) - assert_equals(list(nd.iterkeys()), nd.keys()) + if PY2: + assert_equals(list(iterator), []) + assert_equals(list(nd.iterkeys()), nd.keys()) def test_itervalues_and_values(self): nd = NormalizedDict({'A': 1, 'b': 3, 'C': 2}) - iterator = nd.itervalues() + iterator = nd.itervalues() if PY2 else nd.values() assert_false(isinstance(iterator, list)) assert_equals(list(iterator), [1, 3, 2]) - assert_equals(list(iterator), []) - assert_equals(list(nd.itervalues()), nd.values()) + if PY2: + assert_equals(list(iterator), []) + assert_equals(list(nd.itervalues()), nd.values()) def test_iteritems_and_items(self): nd = NormalizedDict({'A': 1, 'b': 2, 'C': 3}) - iterator = nd.iteritems() + iterator = nd.iteritems() if PY2 else nd.items() assert_false(isinstance(iterator, list)) assert_equals(list(iterator), [('A', 1), ('b', 2), ('C', 3)]) - assert_equals(list(iterator), []) - assert_equals(list(nd.iteritems()), nd.items()) + if PY2: + assert_equals(list(iterator), []) + assert_equals(list(nd.iteritems()), nd.items()) def test_keys_values_and_items_are_returned_in_same_order(self): nd = NormalizedDict() for i, c in enumerate('abcdefghijklmnopqrstuvwxyz0123456789!"#%&/()=?'): nd[c.upper()] = i nd[c+str(i)] = 1 - assert_equals(nd.items(), list(zip(nd.keys(), nd.values()))) - assert_equals(list(nd.iteritems()), list(zip(nd.iterkeys(), nd.itervalues()))) + assert_equals(list(nd.items()), list(zip(nd.keys(), nd.values()))) + if PY2: + assert_equals(list(nd.iteritems()), list(zip(nd.iterkeys(), nd.itervalues()))) def test_eq(self): self._verify_eq(NormalizedDict(), NormalizedDict()) From 635e41338f7a0c958c9ae44d32eaa7fe776acbaf Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 29 May 2015 00:36:59 +0200 Subject: [PATCH 200/214] [python3] atest: Create List of dict.items() for comparing --- atest/robot/output/processing_output.robot | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atest/robot/output/processing_output.robot b/atest/robot/output/processing_output.robot index 1f9ba73a7ba..1d34ed3c0c2 100644 --- a/atest/robot/output/processing_output.robot +++ b/atest/robot/output/processing_output.robot @@ -23,7 +23,8 @@ Directory Suite Should Be Equal ${SUITE.doc} Something Should Be Equal ${SUITE.metadata['x']} y Should Be Equal ${SUITE.metadata['a']} b - Should Be True ${SUITE.metadata.items()} == [('a', 'b'), ('x', 'y')] + ${items} = Create List @{SUITE.metadata.items()} + Should Be True ${items} == [('a', 'b'), ('x', 'y')] Check Suite Got From misc/suites/ Directory Minimal hand-created output From bcc30bc914643fc493b9b6e3a6d8f918179d9b49 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 8 Jun 2015 21:40:53 +0200 Subject: [PATCH 201/214] [python3] running.namespace: fixed basestring check --- src/robot/running/namespace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/robot/running/namespace.py b/src/robot/running/namespace.py index 087131d9cc8..4ee1821b401 100644 --- a/src/robot/running/namespace.py +++ b/src/robot/running/namespace.py @@ -222,7 +222,7 @@ def __init__(self, user_keywords): def get_library(self, name_or_instance): try: - if isinstance(name_or_instance, basestring): + if isinstance(name_or_instance, string_types): return self.libraries[name_or_instance.replace(' ', '')] else: return self._get_lib_by_instance(name_or_instance) From 27122e5568491d54d01bb6739e3aff7e7fe3e011 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 8 Jun 2015 21:53:12 +0200 Subject: [PATCH 202/214] [python3] atest: fixed print in Reloadable.py --- .../standard_libraries/builtin/reload_library/Reloadable.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/atest/testdata/standard_libraries/builtin/reload_library/Reloadable.py b/atest/testdata/standard_libraries/builtin/reload_library/Reloadable.py index 1b3faa015db..704d846febf 100644 --- a/atest/testdata/standard_libraries/builtin/reload_library/Reloadable.py +++ b/atest/testdata/standard_libraries/builtin/reload_library/Reloadable.py @@ -1,3 +1,5 @@ +from __future__ import print_function + from robot.utils import NormalizedDict from robot.libraries.BuiltIn import BuiltIn @@ -22,7 +24,7 @@ def get_keyword_documentation(self, name): return 'Doc for %s with args %s' % (name, ', '.join(KEYWORDS[name])) def run_keyword(self, name, args): - print "Running keyword '%s' with arguments %s." % (name, args) + print("Running keyword '%s' with arguments %s." % (name, args)) assert name in KEYWORDS if name == 'add_keyword': KEYWORDS[args[0]] = args[1:] From 3ac06eae1e1169cdcdb246996bf1096b553f10d9 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Mon, 8 Jun 2015 22:07:54 +0200 Subject: [PATCH 203/214] [python3] atest: Process.Start Process Preferences: fixed print in ${COMMAND} --- .../standard_libraries/process/start_process_preferences.robot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atest/robot/standard_libraries/process/start_process_preferences.robot b/atest/robot/standard_libraries/process/start_process_preferences.robot index 59522b62095..ec07fe95162 100644 --- a/atest/robot/standard_libraries/process/start_process_preferences.robot +++ b/atest/robot/standard_libraries/process/start_process_preferences.robot @@ -4,7 +4,7 @@ Force Tags regression pybot jybot Resource process_resource.robot *** Variables *** -${COMMAND} python -c "import os; print os.path.abspath(os.curdir);" +${COMMAND} python -c "import os; print(os.path.abspath(os.curdir));" *** Test Cases *** Explicitly run Operating System library keyword From 318b51ee795b6115c7eeb56bf8664fe36bc89cfe Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 29 Jul 2015 23:44:44 +0200 Subject: [PATCH 204/214] [python3] parsing.settings: fixed accidentally removed six.PY3 import --- src/robot/parsing/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/robot/parsing/settings.py b/src/robot/parsing/settings.py index 1ea55a29d26..6198b64421e 100644 --- a/src/robot/parsing/settings.py +++ b/src/robot/parsing/settings.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import PY3 + from robot.utils import is_string from .comments import Comment From 41c7c13dfa1c99832a47d5059ab51b9646b4f384 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 29 Jul 2015 23:46:11 +0200 Subject: [PATCH 205/214] [python3] utils.Matcher.__bool__ --- src/robot/utils/match.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/robot/utils/match.py b/src/robot/utils/match.py index cd86dbf1c1f..3349926bc44 100644 --- a/src/robot/utils/match.py +++ b/src/robot/utils/match.py @@ -55,9 +55,13 @@ def match(self, string): def match_any(self, strings): return any(self.match(s) for s in strings) - def __nonzero__(self): + def __bool__(self): return bool(self._normalize(self.pattern)) + #PY2 + def __nonzero__(self): + return self.__bool__() + class MultiMatcher(object): From 7b2f77fbda0c597dd82d13da9554e8cdba5f7e05 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 29 Jul 2015 23:47:00 +0200 Subject: [PATCH 206/214] [python3] BuiltIn: six.moves.StringIO --- src/robot/libraries/BuiltIn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/robot/libraries/BuiltIn.py b/src/robot/libraries/BuiltIn.py index f7ff2eebf7f..ff1df5a0e83 100644 --- a/src/robot/libraries/BuiltIn.py +++ b/src/robot/libraries/BuiltIn.py @@ -19,7 +19,7 @@ from six.moves import range import token from tokenize import generate_tokens, untokenize -from StringIO import StringIO +from six.moves import StringIO from robot.api import logger from robot.errors import (ContinueForLoop, DataError, ExecutionFailed, From 7fa032144d5180c625bfbd583fe5d61cff91c9ee Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Wed, 29 Jul 2015 23:47:58 +0200 Subject: [PATCH 207/214] [python3] atest: six.moves.UserDict --- atest/testdata/standard_libraries/builtin/evaluate.robot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atest/testdata/standard_libraries/builtin/evaluate.robot b/atest/testdata/standard_libraries/builtin/evaluate.robot index 0f0c46da8f3..6a01c104dd6 100644 --- a/atest/testdata/standard_libraries/builtin/evaluate.robot +++ b/atest/testdata/standard_libraries/builtin/evaluate.robot @@ -47,7 +47,7 @@ Evaluate with Get Variables Namespace Should be Equal ${res} ${True} Evaluate with Non-dict Namespace - ${ns} = Evaluate UserDict.UserDict(foo='value') modules=UserDict + ${ns} = Evaluate six.moves.UserDict(foo='value') modules=six ${res} = Evaluate foo == 'value' namespace=${ns} Should be Equal ${res} ${True} From a5bd656012e30eb99a9d4d9885367ddfdec6740e Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 30 Jul 2015 19:54:56 +0200 Subject: [PATCH 208/214] [python3] BuiltIn._handle_variables_in_expression: avoid TypeError on StringIO init --- src/robot/libraries/BuiltIn.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/robot/libraries/BuiltIn.py b/src/robot/libraries/BuiltIn.py index ff1df5a0e83..fd5ca16bd9d 100644 --- a/src/robot/libraries/BuiltIn.py +++ b/src/robot/libraries/BuiltIn.py @@ -2661,7 +2661,11 @@ def evaluate(self, expression, modules=None, namespace=None): def _handle_variables_in_expression(self, expression, variables): tokens = [] variable_started = seen_variable = False - generated = generate_tokens(StringIO(expression).readline) + if PY3: # str() to avoid TypeError + stream = StringIO(str(expression)) + else: + stream = StringIO(expression) + generated = generate_tokens(stream.readline) for toknum, tokval, _, _, _ in generated: if variable_started: if toknum == token.NAME: From 769c37c4e1bb63d5008e869e1145f24f0725f783 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 30 Jul 2015 20:33:49 +0200 Subject: [PATCH 209/214] [python3] atest: Cli.Model Modifiers: Regexp check for ImportError in stderr --- atest/robot/cli/model_modifiers/pre_rebot.robot | 7 ++++--- .../robot/cli/model_modifiers/pre_rebot_when_running.robot | 7 ++++--- atest/robot/cli/model_modifiers/pre_run.robot | 7 ++++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/atest/robot/cli/model_modifiers/pre_rebot.robot b/atest/robot/cli/model_modifiers/pre_rebot.robot index eca965263c8..ab853f42e8c 100644 --- a/atest/robot/cli/model_modifiers/pre_rebot.robot +++ b/atest/robot/cli/model_modifiers/pre_rebot.robot @@ -32,9 +32,10 @@ Modifier with arguments separated with ';' Non-existing modifier Run Rebot --prerebotmod NobodyHere -l ${LOG} ${MODIFIED OUTPUT} - Stderr Should Match - ... [ ERROR ] Importing model modifier 'NobodyHere' failed: ImportError: - ... No module named ?NobodyHere?\nTraceback (most recent call last):\n* + Check Stderr Matches Regexp + ... \\[ ERROR \\] Importing model modifier 'NobodyHere' failed: + ... ImportError: No module named '?NobodyHere'?\\nTraceback + ... \\(most recent call last\\):\\n(.|\\n)* Output should not be modified Log should not be modified diff --git a/atest/robot/cli/model_modifiers/pre_rebot_when_running.robot b/atest/robot/cli/model_modifiers/pre_rebot_when_running.robot index 7a9c4258289..a2e2cc7fee5 100644 --- a/atest/robot/cli/model_modifiers/pre_rebot_when_running.robot +++ b/atest/robot/cli/model_modifiers/pre_rebot_when_running.robot @@ -31,9 +31,10 @@ Pre-run and pre-rebot modifiers together Non-existing modifier Run Tests --prerebotmodifier NobodyHere -l ${LOG} ${TEST DATA} - Stderr Should Match - ... [ ERROR ] Importing model modifier 'NobodyHere' failed: ImportError: - ... No module named ?NobodyHere?\nTraceback (most recent call last):\n* + Check Stderr Matches Regexp + ... \\[ ERROR \\] Importing model modifier 'NobodyHere' failed: + ... ImportError: No module named '?NobodyHere'?\\nTraceback + ... \\(most recent call last\\):\\n(.|\\n)* Output should not be modified Log should not be modified diff --git a/atest/robot/cli/model_modifiers/pre_run.robot b/atest/robot/cli/model_modifiers/pre_run.robot index e5d0b3342a8..2c41eee7fb4 100644 --- a/atest/robot/cli/model_modifiers/pre_run.robot +++ b/atest/robot/cli/model_modifiers/pre_run.robot @@ -25,9 +25,10 @@ Modifier with arguments separated with ';' Non-existing modifier Run Tests --prerunmodifier NobodyHere -l ${LOG} ${TEST DATA} - Stderr Should Match - ... [ ERROR ] Importing model modifier 'NobodyHere' failed: ImportError: - ... No module named ?NobodyHere?\nTraceback (most recent call last):\n* + Check Stderr Matches Regexp + ... \\[ ERROR \\] Importing model modifier 'NobodyHere' failed: + ... ImportError: No module named '?NobodyHere'?\\nTraceback + ... \\(most recent call last\\):\\n(.|\\n)* Output should not be modified Log should not be modified From ee46a6875b087389379b13585fce436a14715466 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Thu, 30 Jul 2015 22:55:02 +0200 Subject: [PATCH 210/214] [python3] more __nonzero__ --> __bool__ --- src/robot/result/flattenkeywordmatcher.py | 18 +++++++++++++++--- src/robot/variables/splitter.py | 8 ++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/robot/result/flattenkeywordmatcher.py b/src/robot/result/flattenkeywordmatcher.py index b964161b5d2..b6c63405182 100644 --- a/src/robot/result/flattenkeywordmatcher.py +++ b/src/robot/result/flattenkeywordmatcher.py @@ -38,9 +38,13 @@ def __init__(self, flatten): def match(self, kwtype): return kwtype in self._types - def __nonzero__(self): + def __bool__(self): return bool(self._types) + #PY2 + def __nonzero__(self): + return self.__bool__() + class FlattenByNameMatcher(object): @@ -54,9 +58,13 @@ def match(self, kwname, libname=None): name = '%s.%s' % (libname, kwname) if libname else kwname return self._matcher.match(name) - def __nonzero__(self): + def __bool__(self): return bool(self._matcher) + #PY2 + def __nonzero__(self): + return self.__bool__() + class FlattenByTagMatcher(object): @@ -69,5 +77,9 @@ def __init__(self, flatten): def match(self, kwtags): return self._matcher.match(kwtags) - def __nonzero__(self): + def __bool__(self): return bool(self._matcher) + + #PY2 + def __nonzero__(self): + return self.__bool__() diff --git a/src/robot/variables/splitter.py b/src/robot/variables/splitter.py index 431f060903f..498611ab133 100644 --- a/src/robot/variables/splitter.py +++ b/src/robot/variables/splitter.py @@ -176,10 +176,14 @@ def __iter__(self): def __len__(self): return sum(1 for _ in self) - def __nonzero__(self): + def __bool__(self): try: - iter(self).next() + next(iter(self)) except StopIteration: return False else: return True + + #PY2 + def __nonzero__(self): + return self.__bool__() From 509fc1fa45c79e0098f205d03c2e014820efc6fd Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 31 Jul 2015 17:54:28 +0200 Subject: [PATCH 211/214] [python3] Fixed an unresolved merge conflict --- atest/testdata/standard_libraries/collections/list.robot | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/atest/testdata/standard_libraries/collections/list.robot b/atest/testdata/standard_libraries/collections/list.robot index 5e16ec9fa86..fdb9bf60dcb 100644 --- a/atest/testdata/standard_libraries/collections/list.robot +++ b/atest/testdata/standard_libraries/collections/list.robot @@ -206,17 +206,10 @@ List Should Not Contain Value, Value Found And Own Error Message List Should Not Contain Value ${L1} 1 My error message! List Should Not Contain Duplicates With No Duplicates -<<<<<<< HEAD - ${iterable} ${tuple} = Evaluate iter(range(100)), (0, 1, 2, '0', '1', '2') - : FOR ${list} IN ${L0} ${L1} ${L2} ${L3} ${L4} - ... ${iterable} ${tuple} - \ List Should Not Contain Duplicates ${list} -======= - ${iterable} ${tuple} = Evaluate xrange(100), (0, 1, 2, '0', '1', '2') + ${iterable} ${tuple} = Evaluate iter(range(100)), (0, 1, 2, '0', '1', '2') : FOR ${list} IN ${L0} ${L1} ${L2} ${L3} ... ${L4} ${iterable} ${tuple} \ List Should Not Contain Duplicates ${list} ->>>>>>> 65c2bdb List Should Not Contain Duplicates Is Case And Space Sensitive ${list} = Create List item ITEM i tem i t e m ITE_m From 0aa5f2e9fc8c5d382f92930053bb9f826e9c2866 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 31 Jul 2015 17:55:24 +0200 Subject: [PATCH 212/214] [python3] libraries.Deprecated*: six.with_metaclass --- src/robot/libraries/DeprecatedBuiltIn.py | 5 +++-- src/robot/libraries/DeprecatedOperatingSystem.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/robot/libraries/DeprecatedBuiltIn.py b/src/robot/libraries/DeprecatedBuiltIn.py index 6870a3fa673..8b7f97413f6 100644 --- a/src/robot/libraries/DeprecatedBuiltIn.py +++ b/src/robot/libraries/DeprecatedBuiltIn.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import with_metaclass + import re import fnmatch @@ -46,8 +48,7 @@ def deprecated(self, *args): return deprecated -class DeprecatedBuiltIn(object): - __metaclass__ = deprecator +class DeprecatedBuiltIn(with_metaclass(deprecator, object)): ROBOT_LIBRARY_SCOPE = 'GLOBAL' diff --git a/src/robot/libraries/DeprecatedOperatingSystem.py b/src/robot/libraries/DeprecatedOperatingSystem.py index 9c510eb8bd4..e619e5dddc6 100644 --- a/src/robot/libraries/DeprecatedOperatingSystem.py +++ b/src/robot/libraries/DeprecatedOperatingSystem.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from six import with_metaclass + from .DeprecatedBuiltIn import deprecator from .OperatingSystem import OperatingSystem @@ -19,8 +21,7 @@ OS = OperatingSystem() -class DeprecatedOperatingSystem(object): - __metaclass__ = deprecator +class DeprecatedOperatingSystem(with_metaclass(deprecator, object)): ROBOT_LIBRARY_SCOPE = 'GLOBAL' From 39f38e83442a1749dd08633cdc67405046e8bb32 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 31 Jul 2015 17:56:23 +0200 Subject: [PATCH 213/214] [python3] updated TagStat.__lt__ --- src/robot/model/stats.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/robot/model/stats.py b/src/robot/model/stats.py index 51a9e7d91c4..d3f0ff62cc0 100644 --- a/src/robot/model/stats.py +++ b/src/robot/model/stats.py @@ -182,11 +182,12 @@ def __cmp__(self, other): or Stat.__cmp__(self, other) def __lt__(self, other): - key = (other.critical, other.non_critical, bool(other.combined), - self._norm_name) - other_key = (self.critical, self.non_critical, bool(self.combined), - other._norm_name) - return key < other_key + key = (self.critical, self.non_critical, bool(self.combined)) + other_key = (other.critical, other.non_critical, + bool(other.combined)) + if other_key == key: + return Stat.__lt__(self, other) + return other_key < key #TODO: Necessary? See commented Stat.__eq__ ## def __eq__(self, other): From 7aa16338ce2120cb082605cf548c0794956ec901 Mon Sep 17 00:00:00 2001 From: Stefan Zimmermann Date: Fri, 31 Jul 2015 22:24:21 +0200 Subject: [PATCH 214/214] [python3] atest: ListenImports: replaced basestring check with is_string() --- atest/testresources/listeners/ListenImports.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/atest/testresources/listeners/ListenImports.py b/atest/testresources/listeners/ListenImports.py index 34d01fb2b67..c19989d31bb 100644 --- a/atest/testresources/listeners/ListenImports.py +++ b/atest/testresources/listeners/ListenImports.py @@ -1,5 +1,7 @@ import os +from robot.utils import is_string + class ListenImports(object): ROBOT_LISTENER_API_VERSION = 2 @@ -24,7 +26,7 @@ def _imported(self, import_type, name, attrs): def _pretty(self, entry): if isinstance(entry, list): return '[%s]' % ', '.join(entry) - if isinstance(entry, basestring) and os.path.isabs(entry): + if is_string(entry) and os.path.isabs(entry): entry = entry.replace('$py.class', '.py').replace('.pyc', '.py') tokens = entry.split(os.sep) index = -1 if tokens[-1] != '__init__.py' else -2