Skip to content

Commit faad535

Browse files
committed
Initial version
0 parents  commit faad535

File tree

9 files changed

+379
-0
lines changed

9 files changed

+379
-0
lines changed

atest/DynamicLibrary.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from robotlibcore import DynamicCore
2+
3+
import librarycomponents
4+
5+
6+
class DynamicLibrary(DynamicCore):
7+
"""General library documentation."""
8+
class_attribute = 'not keyword'
9+
10+
def __init__(self, arg=None):
11+
"""Library init doc."""
12+
components = [librarycomponents,
13+
librarycomponents.Names(),
14+
librarycomponents.Arguments(),
15+
librarycomponents.DocsAndTags()]
16+
DynamicCore.__init__(self, components)
17+
self.instance_attribute = 'not keyword'

atest/HybridLibrary.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from robotlibcore import HybridCore
2+
3+
import librarycomponents
4+
5+
6+
class HybridLibrary(HybridCore):
7+
"""General library documentation."""
8+
class_attribute = 'not keyword'
9+
10+
def __init__(self):
11+
components = [librarycomponents,
12+
librarycomponents.Names(),
13+
librarycomponents.Arguments(),
14+
librarycomponents.DocsAndTags()]
15+
HybridCore.__init__(self, components)
16+
self.instance_attribute = 'not keyword'

atest/librarycomponents.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
from __future__ import print_function
2+
3+
from robotlibcore import keyword
4+
5+
6+
@keyword
7+
def function():
8+
return 1
9+
10+
11+
class Names(object):
12+
attribute = 'not keyword'
13+
14+
@keyword
15+
def method(self):
16+
return 2
17+
18+
@keyword('Custom name')
19+
def _whatever(self):
20+
return 3
21+
22+
def not_keyword(self):
23+
pass
24+
25+
26+
class Arguments(object):
27+
28+
@keyword
29+
def mandatory(self, arg1, arg2):
30+
return self.format_args(arg1, arg2)
31+
32+
@keyword
33+
def defaults(self, arg1, arg2='default', arg3=3):
34+
return self.format_args(arg1, arg2, arg3)
35+
36+
@keyword
37+
def varargs_and_kwargs(self, *args, **kws):
38+
return self.format_args(*args, **kws)
39+
40+
@keyword
41+
def all_arguments(self, mandatory, default='value', *varargs, **kwargs):
42+
return self.format_args(mandatory, default, *varargs, **kwargs)
43+
44+
@keyword('Embedded arguments "${here}"')
45+
def embedded(self, arg):
46+
assert arg == 'work', arg
47+
48+
def format_args(self, *args, **kwargs):
49+
def ru(item):
50+
return repr(item).lstrip('u')
51+
args = [ru(a) for a in args]
52+
kwargs = ['%s=%s' % (k, ru(kwargs[k])) for k in sorted(kwargs)]
53+
return ', '.join(args + kwargs)
54+
55+
56+
class DocsAndTags(object):
57+
58+
@keyword
59+
def one_line_doc(self):
60+
"""I got doc!"""
61+
62+
@keyword
63+
def multi_line_doc(self):
64+
"""I got doc!
65+
66+
With multiple lines!!
67+
Yeah!!!!
68+
"""
69+
70+
@keyword(tags=['tag', 'another tag'])
71+
def tags(self):
72+
pass
73+
74+
@keyword(tags=['tag'])
75+
def doc_and_tags(self):
76+
"""I got doc!"""

atest/run.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env python
2+
3+
from __future__ import print_function
4+
from os.path import abspath, dirname, join
5+
import sys
6+
7+
from robot import run, rebot
8+
from robotstatuschecker import process_output
9+
10+
11+
curdir = dirname(abspath(__file__))
12+
outdir = join(curdir, 'results')
13+
tests = join(curdir, 'tests.robot')
14+
sys.path.insert(0, join(curdir, '..', 'src'))
15+
for variant in ['Hybrid', 'Dynamic']:
16+
output = join(outdir, variant + '.xml')
17+
rc = run(tests, name=variant, variable='LIBRARY:%sLibrary' % variant,
18+
output=output, report=None, log=None)
19+
if rc > 250:
20+
sys.exit(rc)
21+
process_output(output, verbose=False)
22+
print('\nCombining results.')
23+
rc = rebot(join(outdir, 'Hybrid.xml'), join(outdir, 'Dynamic.xml'),
24+
name='Acceptance Tests', outputdir=outdir)
25+
if rc == 0:
26+
print('\nAll tests passed/failed as expected.')
27+
else:
28+
print('\n%d test%s failed.' % (rc, 's' if rc != 1 else ''))
29+
sys.exit(rc)

atest/tests.robot

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
*** Settings ***
2+
Library ${LIBRARY}.py
3+
4+
*** Variables ***
5+
${LIBRARY} DynamicLibrary
6+
7+
*** Test Cases ***
8+
Keyword names
9+
Function
10+
FUNCTION
11+
Method
12+
Custom name
13+
Cust omna me
14+
15+
Method without @keyword are not keyowrds
16+
[Documentation] FAIL No keyword with name 'Not keyword' found.
17+
Not keyword
18+
19+
Arguments
20+
[Template] Return value should be
21+
'foo', 'bar' Mandatory foo bar
22+
'foo', 'default', 3 Defaults foo
23+
'foo', 2, 3 Defaults foo ${2}
24+
'a', 'b', 'c' Defaults a b c
25+
26+
Named arguments
27+
[Template] Return value should be
28+
'foo', 'bar' Mandatory foo arg2=bar
29+
'1', 2 Mandatory arg2=${2} arg1=1
30+
'x', 'default', 'y' Defaults x arg3=y
31+
32+
Varargs and kwargs
33+
[Template] Return value should be
34+
${EMPTY} Varargs and kwargs
35+
'a', 'b', 'c' Varargs and kwargs a b c
36+
a\='1', b\=2 Varargs and kwargs a=1 b=${2}
37+
'a', 'b\=b', c\='c' Varargs and kwargs a b\=b c=c
38+
39+
Embedded arguments
40+
[Documentation] FAIL Work but this fails
41+
Embedded arguments "work"
42+
embeDded ArgumeNtS "Work but this fails"
43+
44+
*** Keywords ***
45+
Return value should be
46+
[Arguments] ${expected} ${keyword} @{args} &{kwargs}
47+
${result} = Run Keyword ${keyword} @{args} &{kwargs}
48+
Should Be Equal ${result} ${expected}

requirements-dev.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pytest
2+
robotstatuschecker

src/robotlibcore.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import inspect
2+
import sys
3+
4+
try:
5+
from robot.api.deco import keyword
6+
except ImportError: # Support RF < 2.9
7+
def keyword(name=None, tags=()):
8+
if callable(name):
9+
return keyword()(name)
10+
def decorator(func):
11+
func.robot_name = name
12+
func.robot_tags = tags
13+
return func
14+
return decorator
15+
16+
17+
PY2 = sys.version_info < (3,)
18+
19+
20+
class HybridCore(object):
21+
22+
def __init__(self, libraries):
23+
self.keywords = dict(self._find_keywords(libraries))
24+
25+
def _find_keywords(self, libraries):
26+
for library in libraries:
27+
for name, func in self._get_members(library):
28+
if callable(func) and hasattr(func, 'robot_name'):
29+
kw_name = func.robot_name or name
30+
yield kw_name, getattr(library, name)
31+
32+
def _get_members(self, library):
33+
# Avoid calling properties by getting members from class, not instance.
34+
if inspect.isclass(library):
35+
library = type(library)
36+
return inspect.getmembers(library)
37+
38+
def __getattr__(self, name):
39+
if name in self.keywords:
40+
return self.keywords[name]
41+
raise AttributeError('{!r} object has no attribute {!r}'
42+
.format(type(self).__name__, name))
43+
44+
def __dir__(self):
45+
if PY2:
46+
my_attrs = dir(type(self)) + list(self.__dict__)
47+
else:
48+
my_attrs = super().__dir__()
49+
return sorted(set(my_attrs + list(self.keywords)))
50+
51+
def get_keyword_names(self):
52+
return sorted(self.keywords)
53+
54+
55+
class DynamicCore(HybridCore):
56+
57+
def run_keyword(self, name, args, kwargs):
58+
return self.keywords[name](*args, **kwargs)
59+
60+
def get_keyword_arguments(self, name):
61+
kw = self.keywords[name] if name != '__init__' else self.__init__
62+
args, defaults, varargs, kwargs = self._get_arg_spec(kw)
63+
args += ['{}={}'.format(name, value) for name, value in defaults]
64+
if varargs:
65+
args.append('*{}'.format(varargs))
66+
if kwargs:
67+
args.append('**{}'.format(kwargs))
68+
return args
69+
70+
def _get_arg_spec(self, kw):
71+
spec = inspect.getargspec(kw)
72+
args = spec.args[1:] if inspect.ismethod(kw) else spec.args # drop self
73+
defaults = spec.defaults or ()
74+
nargs = len(args) - len(defaults)
75+
mandatory = args[:nargs]
76+
defaults = zip(args[nargs:], defaults)
77+
return mandatory, defaults, spec.varargs, spec.keywords
78+
79+
def get_keyword_documentation(self, name):
80+
doc, tags = self._get_doc_and_tags(name)
81+
doc = doc or ''
82+
tags = 'Tags: {}'.format(', '.join(tags)) if tags else ''
83+
sep = '\n\n' if doc and tags else ''
84+
return doc + sep + tags
85+
86+
def _get_doc_and_tags(self, name):
87+
if name == '__intro__':
88+
return inspect.getdoc(self), None
89+
if name == '__init__':
90+
return inspect.getdoc(self.__init__), None
91+
kw = self.keywords[name]
92+
return inspect.getdoc(kw), kw.robot_tags

utest/run.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env python
2+
3+
from os.path import abspath, dirname, join
4+
import sys
5+
6+
import pytest
7+
8+
9+
curdir = dirname(abspath(__file__))
10+
sys.path.insert(0, join(curdir, '..', 'src'))
11+
sys.path.insert(0, join(curdir, '..', 'atest'))
12+
rc = pytest.main(sys.argv[1:] + ['-p', 'no:cacheprovider', curdir])
13+
sys.exit(rc)

utest/test_robotlibcore.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import pytest
2+
3+
from HybridLibrary import HybridLibrary
4+
from DynamicLibrary import DynamicLibrary
5+
6+
7+
def test_keyword_names():
8+
expected = ['Custom name',
9+
'Embedded arguments "${here}"',
10+
'all_arguments',
11+
'defaults',
12+
'doc_and_tags',
13+
'function',
14+
'mandatory',
15+
'method',
16+
'multi_line_doc',
17+
'one_line_doc',
18+
'tags',
19+
'varargs_and_kwargs']
20+
assert HybridLibrary().get_keyword_names() == expected
21+
assert DynamicLibrary().get_keyword_names() == expected
22+
23+
24+
def test_dir():
25+
expected = ['Custom name',
26+
'Embedded arguments "${here}"',
27+
'all_arguments',
28+
'class_attribute',
29+
'defaults',
30+
'doc_and_tags',
31+
'function',
32+
'get_keyword_arguments',
33+
'get_keyword_documentation',
34+
'get_keyword_names',
35+
'instance_attribute',
36+
'keywords',
37+
'mandatory',
38+
'method',
39+
'multi_line_doc',
40+
'one_line_doc',
41+
'run_keyword',
42+
'tags',
43+
'varargs_and_kwargs']
44+
assert [a for a in dir(DynamicLibrary()) if a[0] != '_'] == expected
45+
expected = [e for e in expected if e not in ('get_keyword_arguments',
46+
'get_keyword_documentation',
47+
'run_keyword')]
48+
assert [a for a in dir(HybridLibrary()) if a[0] != '_'] == expected
49+
50+
51+
def test_getattr():
52+
for lib in [HybridLibrary(), DynamicLibrary()]:
53+
assert lib.class_attribute == 'not keyword'
54+
assert lib.instance_attribute == 'not keyword'
55+
assert lib.function() == 1
56+
assert lib.method() == 2
57+
assert getattr(lib, 'Custom name')() == 3
58+
with pytest.raises(AttributeError) as excinfo:
59+
lib.attribute
60+
assert str(excinfo.value) == \
61+
"'%s' object has no attribute 'attribute'" % type(lib).__name__
62+
63+
64+
def test_get_keyword_arguments():
65+
args = DynamicLibrary().get_keyword_arguments
66+
assert args('mandatory') == ['arg1', 'arg2']
67+
assert args('defaults') == ['arg1', 'arg2=default', 'arg3=3']
68+
assert args('varargs_and_kwargs') == ['*args', '**kws']
69+
assert args('all_arguments') == ['mandatory', 'default=value', '*varargs', '**kwargs']
70+
assert args('__init__') == ['arg=None']
71+
72+
73+
def test_get_keyword_documentation():
74+
doc = DynamicLibrary().get_keyword_documentation
75+
assert doc('function') == ''
76+
assert doc('method') == ''
77+
assert doc('one_line_doc') == 'I got doc!'
78+
assert doc('multi_line_doc') == 'I got doc!\n\nWith multiple lines!!\nYeah!!!!'
79+
assert doc('__intro__') == 'General library documentation.'
80+
assert doc('__init__') == 'Library init doc.'
81+
82+
83+
def test_tags():
84+
doc = DynamicLibrary().get_keyword_documentation
85+
assert doc('tags') == 'Tags: tag, another tag'
86+
assert doc('doc_and_tags') == 'I got doc!\n\nTags: tag'

0 commit comments

Comments
 (0)