Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions atest/libs/FullDynamicLibrary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import sys
from StaticApiLibrary import AcceptanceTestLibrary
from util.dynamiclibrary import DynamicApi


if __name__ == '__main__':
from robotremoteserver import RobotRemoteServer

_library = DynamicApi(AcceptanceTestLibrary(), True, True)
RobotRemoteServer(_library, *sys.argv[1:])
10 changes: 10 additions & 0 deletions atest/libs/MinimalDynamicLibrary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import sys
from StaticApiLibrary import AcceptanceTestLibrary
from util.dynamiclibrary import DynamicApi


if __name__ == '__main__':
from robotremoteserver import RobotRemoteServer

_library = DynamicApi(AcceptanceTestLibrary(), False, False)
RobotRemoteServer(_library, *sys.argv[1:])
File renamed without changes.
Empty file added atest/libs/util/__init__.py
Empty file.
48 changes: 48 additions & 0 deletions atest/libs/util/dynamiclibrary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import inspect


class DynamicApi:
"""A wrapper to make static API libraries use the dynamic API."""
def __init__(self, library, kwdoc=True, argsdoc=True):
self._library = library
if kwdoc:
self.get_keyword_documentation = self._get_keyword_documentation
if argsdoc:
self.get_keyword_arguments = self._get_keyword_arguments

def get_keyword_names(self):
return [attr for attr in dir(self._library) if attr[0] != '_'
and inspect.isroutine(getattr(self._library, attr))]

def run_keyword(self, name, args):
return self._get_keyword(name)(*args)

def _get_keyword_documentation(self, name):
if name == '__intro__':
return inspect.getdoc(self._library) or ''
if name == '__init__' and inspect.ismodule(self._library):
return ''
return inspect.getdoc(self._get_keyword(name)) or ''

def _get_keyword_arguments(self, name):
kw = self._get_keyword(name)
if not kw:
return []
return self._arguments_from_kw(kw)

def _get_keyword(self, name):
kw = getattr(self._library, name, None)
if inspect.isroutine(kw):
return kw
return None

def _arguments_from_kw(self, kw):
args, varargs, _, defaults = inspect.getargspec(kw)
if inspect.ismethod(kw):
args = args[1:] # drop 'self'
if defaults:
args, names = args[:-len(defaults)], args[-len(defaults):]
args += ['%s=%s' % (n, d) for n, d in zip(names, defaults)]
if varargs:
args.append('*%s' % varargs)
return args
23 changes: 16 additions & 7 deletions atest/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

"""Script for running the remote server tests using different interpreters.

usage: run.py server[:runner] [[options] datasources]
usage: run.py server[:runner] [libraryfile=filename] [[options] datasources]

`server` is the only required argument and specifies the server interpreter
to use. `runner` is the interpreter to use for running tests, defaulting to
the server interpreter.
the server interpreter. `libraryfile` is the file name of the test library to
use, defaulting to StaticApiLibrary.py.

By default all tests under `tests` directory are executed. This can be
changed by giving data sources and options explicitly.
Expand All @@ -31,20 +32,28 @@
mkdir(RESULTS)


if len(sys.argv) == 1 or '-h' in sys.argv or '--help' in sys.argv:
clargs = sys.argv[1:]
if not clargs or '-h' in clargs or '--help' in clargs:
sys.exit(__doc__)

interpreters = sys.argv[1]
interpreters = clargs.pop(0)
if ':' in interpreters:
server_interpreter, runner_interpreter = interpreters.rsplit(':', 1)
else:
server_interpreter = runner_interpreter = interpreters
if clargs and clargs[0].startswith('libraryfile='):
library_file = clargs.pop(0).split('=')[1]
else:
library_file = 'StaticApiLibrary.py'

servercontroller.start(server_interpreter)
servercontroller.start(server_interpreter, library_file)

args = [runner_interpreter, '-m', 'robot.run', '--name', interpreters,
name = interpreters + '_-_' + library_file.rsplit('.', 1)[0]
args = [runner_interpreter, '-m', 'robot.run', '--name', name,
'--output', OUTPUT, '--log', 'NONE', '--report', 'NONE']
args.extend(sys.argv[2:] or [join(BASE, 'tests')])
if 'minimal' in library_file.lower():
args.extend(['--exclude', 'argsknown'])
args.extend(clargs or [join(BASE, 'tests')])
print 'Running tests with command:\n%s' % ' '.join(args)
subprocess.call(args)

Expand Down
14 changes: 8 additions & 6 deletions atest/servercontroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
"""Module/script for controlling remote server used in tests.

When used as module, provides `start`, `test`, and `stop` methods.
The server's stdin and stdout streams are redirected to results/server.txt

Command line usage:
servercontroller.py start [interpreter=sys.executable]
servercontroller.py test [port=8270] [attempts=1]
servercontroller.py stop [port=8270]
Usage: servercontroller.py start|stop|test [args]

Note: Starting from CLI leaves the terminal into messed up state.
start args: [interpreter=sys.executable] [library='StaticApiLibrary.py']
test args: [port=8270] [attempts=1]
stop args: [port=8270]

Note: Starting from CLI leaves the terminal in a messed up state.
"""

from __future__ import with_statement
Expand All @@ -25,7 +27,7 @@
BASE = dirname(abspath(__file__))


def start(interpreter=sys.executable, library='atestlibrary.py'):
def start(interpreter=sys.executable, library='StaticApiLibrary.py'):
results = _get_result_directory()
with open(join(results, 'server.txt'), 'w') as output:
server = subprocess.Popen([interpreter, join(BASE, 'libs', library)],
Expand Down
1 change: 1 addition & 0 deletions atest/tests/invalid_argument_counts.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
*** Settings ***
Default Tags argsknown
Resource resource.txt

*** Test Cases ***
Expand Down
2 changes: 1 addition & 1 deletion atest/tests/traceback.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Remote Traceback Is Shown In Log
[Documentation] FAIL My error message
... LOG 1:1 FAIL My error message
... LOG 1:2 DEBUG REGEXP: Traceback \\(most recent call last\\):
... \\s+File "[\\w /\\\\]+atestlibrary.py", line .*
... \\s+File "[\\w: /\\\\]+[^/\\\\]+ibrary.py", line .*
... \\s+raise AssertionError\\(message\\)
Failing My error message

Expand Down
54 changes: 41 additions & 13 deletions src/robotremoteserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import sys
import inspect
import traceback
import re
from StringIO import StringIO
from SimpleXMLRPCServer import SimpleXMLRPCServer
try:
Expand All @@ -34,6 +35,8 @@ class RobotRemoteServer(SimpleXMLRPCServer):
def __init__(self, library, host='localhost', port=8270, allow_stop=True):
SimpleXMLRPCServer.__init__(self, (host, int(port)), logRequests=False)
self._library = library
self._is_dynamic = self._get_routine('run_keyword') and \
self._get_routine('get_keyword_names')
self._allow_stop = allow_stop
self._register_functions()
self._register_signal_handlers()
Expand All @@ -43,8 +46,12 @@ def __init__(self, library, host='localhost', port=8270, allow_stop=True):
def _register_functions(self):
self.register_function(self.get_keyword_names)
self.register_function(self.run_keyword)
self.register_function(self.get_keyword_arguments)
self.register_function(self.get_keyword_documentation)
if not self._is_dynamic or \
(self._is_dynamic and self._get_routine('get_keyword_arguments')):
self.register_function(self.get_keyword_arguments)
if not self._is_dynamic or \
(self._is_dynamic and self._get_routine('get_keyword_documentation')):
self.register_function(self.get_keyword_documentation)
self.register_function(self.stop_remote_server)

def _register_signal_handlers(self):
Expand All @@ -71,20 +78,24 @@ def stop_remote_server(self):
return True

def get_keyword_names(self):
get_kw_names = getattr(self._library, 'get_keyword_names', None) or \
getattr(self._library, 'getKeywordNames', None)
if inspect.isroutine(get_kw_names):
names = get_kw_names()
else:
get_kw_names = self._get_routine('get_keyword_names')
if get_kw_names is None:
names = [attr for attr in dir(self._library) if attr[0] != '_'
and inspect.isroutine(getattr(self._library, attr))]
else:
names = get_kw_names()
return names + ['stop_remote_server']

def run_keyword(self, name, args):
result = {'error': '', 'traceback': '', 'return': ''}
self._intercept_stdout()
try:
return_value = self._get_keyword(name)(*args)
if name == 'stop_remote_server':
return_value = self.stop_remote_server()
elif self._is_dynamic:
return_value = self._get_routine('run_keyword')(name, args)
else:
return_value = self._get_keyword(name)(*args)
except:
result['status'] = 'FAIL'
result['error'], result['traceback'] = self._get_error_details()
Expand All @@ -95,10 +106,23 @@ def run_keyword(self, name, args):
return result

def get_keyword_arguments(self, name):
kw = self._get_keyword(name)
if not kw:
if name == 'stop_remote_server':
return []
return self._arguments_from_kw(kw)
elif self._is_dynamic:
return list(self._get_routine('get_keyword_arguments')(name))
else:
kw = self._get_keyword(name)
if not kw:
return []
return self._arguments_from_kw(kw)

def _get_routine(self, py_name):
repl = lambda x: x.group(1).upper()
for name in [py_name, re.sub('_(.)', repl, py_name)]:
rt = getattr(self._library, name, None)
if inspect.isroutine(rt):
return rt
return None

def _arguments_from_kw(self, kw):
args, varargs, _, defaults = inspect.getargspec(kw)
Expand All @@ -112,15 +136,19 @@ def _arguments_from_kw(self, kw):
return args

def get_keyword_documentation(self, name):
if name == 'stop_remote_server':
return 'Stops the remote server.\n\n' + \
'The server may be configured so that users cannot stop it.'
get_kw_doc = self._get_routine('get_keyword_documentation')
if self._is_dynamic and get_kw_doc:
return get_kw_doc(name)
if name == '__intro__':
return inspect.getdoc(self._library) or ''
if name == '__init__' and inspect.ismodule(self._library):
return ''
return inspect.getdoc(self._get_keyword(name)) or ''

def _get_keyword(self, name):
if name == 'stop_remote_server':
return self.stop_remote_server
kw = getattr(self._library, name, None)
if inspect.isroutine(kw):
return kw
Expand Down
75 changes: 64 additions & 11 deletions utest/test_argsdocs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from test_robotremoteserver import NonServingRemoteServer


class LibraryWithArgsAndDocs:
class StaticLibraryWithArgsAndDocs:
"""Intro doc"""

def __init__(self, i1, i2=1, *i3):
Expand All @@ -17,6 +17,44 @@ def keyword(self, k1, k2=2, *k3):
def no_doc_or_args(self):
pass

class DynamicLibraryWithArgsAndDocs:
"""Unseen intro"""

def __init__(self, i1, i2=1, *i3):
"""Unseen init"""

def get_keyword_names(self):
return ['keyword', 'no_doc_or_args']

def run_keyword(self, name, args):
pass

def get_keyword_arguments(self, name):
return {'keyword':['k1', 'k2=2', '*k3'], 'no_doc_or_args':[]}[name]

def get_keyword_documentation(self, name):
return {'__intro__':'Intro doc', '__init__':'Init doc',
'keyword':'Keyword doc', 'no_doc_or_args':''}[name]

def keyword(self):
"""Unseen keyword doc"""
pass

class CamelCaseLibraryWithArgsAndDocs():

def getKeywordNames(self):
return ['keyword', 'no_doc_or_args']

def runKeyword(self, name, args):
pass

def getKeywordArguments(self, name):
return {'keyword':['k1', 'k2=2', '*k3'], 'no_doc_or_args':[]}[name]

def getKeywordDocumentation(self, name):
return {'__intro__':'Intro doc', '__init__':'Init doc',
'keyword':'Keyword doc', 'no_doc_or_args':''}[name]


def keyword_in_module(m1, m2=3, *m3):
"""Module keyword doc"""
Expand All @@ -38,43 +76,58 @@ def test_init_doc(self):

def test_init_doc_when_old_style_lib_has_no_init(self):
class OldStyleLibraryWithoutInit: pass
self._test_doc('__init__', '', OldStyleLibraryWithoutInit())
self._test_doc('__init__', '', [OldStyleLibraryWithoutInit()])

def test_init_doc_when_new_style_lib_has_no_init(self):
class NewStyleLibraryWithoutInit(object): pass
self._test_doc('__init__', '', NewStyleLibraryWithoutInit())
self._test_doc('__init__', '', [NewStyleLibraryWithoutInit()])

def test_keyword_doc_from_module_keyword(self):
import test_argsdocs
self._test_doc('keyword_in_module', 'Module keyword doc', test_argsdocs)
self._test_doc('keyword_in_module', 'Module keyword doc',
[test_argsdocs])

def test_init_doc_from_module(self):
import test_argsdocs
self._test_doc('__init__', '', test_argsdocs)
self._test_doc('__init__', '', [test_argsdocs])

def test_intro_doc_from_module(self):
import test_argsdocs
self._test_doc('__intro__', 'Module doc - used in tests', test_argsdocs)
self._test_doc('__intro__', 'Module doc - used in tests',
[test_argsdocs])

def _test_doc(self, name, expected, library=LibraryWithArgsAndDocs(None)):
server = NonServingRemoteServer(library)
self.assertEquals(server.get_keyword_documentation(name), expected)
def _test_doc(self, name, expected, libraries=
[StaticLibraryWithArgsAndDocs(None),
DynamicLibraryWithArgsAndDocs(None),
CamelCaseLibraryWithArgsAndDocs()]):
for library in libraries:
server = NonServingRemoteServer(library)
self.assertEquals(server.get_keyword_documentation(name), expected)


class TestArgs(unittest.TestCase):
class TestDynamicArgs(unittest.TestCase):

def test_keyword_args(self):
self._test_args('keyword', ['k1', 'k2=2', '*k3'])

def test_keyword_args_when_no_args(self):
self._test_args('no_doc_or_args', [])

def _test_args(self, name, expected,
library=DynamicLibraryWithArgsAndDocs(None)):
server = NonServingRemoteServer(library)
self.assertEquals(server.get_keyword_arguments(name), expected)


class TestStaticArgs(TestDynamicArgs):

def test_keyword_args_from_module_keyword(self):
import test_argsdocs
self._test_args('keyword_in_module', ['m1', 'm2=3', '*m3'],
test_argsdocs)

def _test_args(self, name, expected, library=LibraryWithArgsAndDocs(None)):
def _test_args(self, name, expected,
library=StaticLibraryWithArgsAndDocs(None)):
server = NonServingRemoteServer(library)
self.assertEquals(server.get_keyword_arguments(name), expected)

Expand Down
Loading