diff --git a/atest/DynamicTypesLibrary.py b/atest/DynamicTypesLibrary.py index 3522813..3cae0a2 100644 --- a/atest/DynamicTypesLibrary.py +++ b/atest/DynamicTypesLibrary.py @@ -3,6 +3,10 @@ from robotlibcore import DynamicCore, keyword +def def_deco(func): + return func + + class DynamicTypesLibrary(DynamicCore): def __init__(self, arg=False): @@ -52,3 +56,8 @@ def keyword_none(self, arg=None): @keyword def is_python_3(self): return sys.version_info >= (3,) + + @keyword + @def_deco + def keyword_with_def_deco(self): + return 1 diff --git a/requirements-dev.txt b/requirements-dev.txt index 112c852..ca8a225 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,5 @@ pytest pytest-cov +pytest-mockito robotstatuschecker flake8 diff --git a/src/robotlibcore.py b/src/robotlibcore.py index a2823e2..68ec838 100644 --- a/src/robotlibcore.py +++ b/src/robotlibcore.py @@ -20,6 +20,7 @@ """ import inspect +import os import sys try: import typing @@ -174,6 +175,34 @@ def __join_defaults_with_types(self, method, types): types[name] = type(value) return types + def get_keyword_source(self, keyword_name): + method = self.__get_keyword(keyword_name) + path = self.__get_keyword_path(method) + line_number = self.__get_keyword_line(method) + if path and line_number: + return '%s:%s' % (path, line_number) + if path: + return path + if line_number: + return ':%s' % line_number + return None + + def __get_keyword_line(self, method): + try: + source, line_number = inspect.getsourcelines(method) + except (OSError, IOError, TypeError): + return None + for line in source: + if line.strip().startswith('def'): + return line_number + line_number += 1 + + def __get_keyword_path(self, method): + try: + return os.path.normpath(inspect.getfile(method)) + except TypeError: + return None + class StaticCore(HybridCore): diff --git a/utest/test_get_keyword_source.py b/utest/test_get_keyword_source.py new file mode 100644 index 0000000..895aa96 --- /dev/null +++ b/utest/test_get_keyword_source.py @@ -0,0 +1,87 @@ +import inspect +from os import path + +import pytest +from DynamicLibrary import DynamicLibrary +from DynamicTypesLibrary import DynamicTypesLibrary +from mockito.matchers import Any + + +@pytest.fixture(scope='module') +def lib(): + return DynamicLibrary() + + +@pytest.fixture(scope='module') +def lib_types(): + return DynamicTypesLibrary() + + +@pytest.fixture(scope='module') +def cur_dir(): + return path.dirname(__file__) + +@pytest.fixture(scope='module') +def lib_path(cur_dir): + return path.normpath(path.join(cur_dir, '..', 'atest', 'DynamicLibrary.py')) + + +@pytest.fixture(scope='module') +def lib_path_components(cur_dir): + return path.normpath(path.join(cur_dir, '..', 'atest', 'librarycomponents.py')) + + +@pytest.fixture(scope='module') +def lib_path_types(cur_dir): + return path.normpath(path.join(cur_dir, '..', 'atest', 'DynamicTypesLibrary.py')) + + +def test_location_in_main(lib, lib_path): + source = lib.get_keyword_source('keyword_in_main') + assert source == '%s:20' % lib_path + + +def test_location_in_class(lib, lib_path_components): + source = lib.get_keyword_source('method') + assert source == '%s:15' % lib_path_components + + +def test_location_in_class_custom_keyword_name(lib, lib_path_components): + source = lib.get_keyword_source('Custom name') + assert source == '%s:19' % lib_path_components + + +def test_no_line_number(lib, lib_path, when): + when(lib)._DynamicCore__get_keyword_line(Any()).thenReturn(None) + source = lib.get_keyword_source('keyword_in_main') + assert source == lib_path + + +def test_no_path(lib, when): + when(lib)._DynamicCore__get_keyword_path(Any()).thenReturn(None) + source = lib.get_keyword_source('keyword_in_main') + assert source == ':20' + + +def test_no_path_and_no_line_number(lib, when): + when(lib)._DynamicCore__get_keyword_path(Any()).thenReturn(None) + when(lib)._DynamicCore__get_keyword_line(Any()).thenReturn(None) + source = lib.get_keyword_source('keyword_in_main') + assert source is None + + +def test_def_in_decorator(lib_types, lib_path_types): + source = lib_types.get_keyword_source('keyword_with_def_deco') + assert source == '%s:62' % lib_path_types + + +def test_error_in_getfile(lib, when): + when(inspect).getfile(Any()).thenRaise(TypeError('Some message')) + source = lib.get_keyword_source('keyword_in_main') + assert source is None + + +def test_error_in_line_number(lib, when, lib_path): + when(inspect).getsourcelines(Any()).thenRaise(IOError('Some message')) + source = lib.get_keyword_source('keyword_in_main') + assert source == lib_path \ No newline at end of file diff --git a/utest/test_robotlibcore.py b/utest/test_robotlibcore.py index 5897d69..160a9ab 100644 --- a/utest/test_robotlibcore.py +++ b/utest/test_robotlibcore.py @@ -31,6 +31,8 @@ def test_dir(): 'Embedded arguments "${here}"', '_DynamicCore__get_arg_spec', '_DynamicCore__get_keyword', + '_DynamicCore__get_keyword_line', + '_DynamicCore__get_keyword_path', '_DynamicCore__get_keyword_tags_supported', '_DynamicCore__get_typing_hints', '_DynamicCore__join_defaults_with_types', @@ -48,6 +50,7 @@ def test_dir(): 'get_keyword_arguments', 'get_keyword_documentation', 'get_keyword_names', + 'get_keyword_source', 'get_keyword_tags', 'get_keyword_types', 'instance_attribute', @@ -65,10 +68,13 @@ def test_dir(): expected = [e for e in expected if e not in ('_DynamicCore__get_typing_hints', '_DynamicCore__get_arg_spec', '_DynamicCore__get_keyword', + '_DynamicCore__get_keyword_line', + '_DynamicCore__get_keyword_path', '_DynamicCore__get_keyword_tags_supported', '_DynamicCore__join_defaults_with_types', 'get_keyword_arguments', 'get_keyword_documentation', + 'get_keyword_source', 'get_keyword_tags', 'run_keyword', 'get_keyword_types')]