Skip to content
Merged
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
9 changes: 9 additions & 0 deletions atest/DynamicTypesLibrary.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
from robotlibcore import DynamicCore, keyword


def def_deco(func):
return func


class DynamicTypesLibrary(DynamicCore):

def __init__(self, arg=False):
Expand Down Expand Up @@ -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
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pytest
pytest-cov
pytest-mockito
robotstatuschecker
flake8
29 changes: 29 additions & 0 deletions src/robotlibcore.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"""

import inspect
import os
import sys
try:
import typing
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this doesn't work correctly if someone has a library like this:

class LambdaLib:
    kw = lambda self: 42

Above is obviously rather strange but from RF point of view it's valid. I see two solutions:

  • Return the original line_number if def line isn't found at all.
  • Just return whatever getsourcelines returns.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bigger problem is that decorators returning different function than the original aren't supported correctly. Same issue on Robot side that the moment:
robotframework/robotframework#3516 (comment)


def __get_keyword_path(self, method):
try:
return os.path.normpath(inspect.getfile(method))
except TypeError:
return None


class StaticCore(HybridCore):

Expand Down
87 changes: 87 additions & 0 deletions utest/test_get_keyword_source.py
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions utest/test_robotlibcore.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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',
Expand All @@ -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')]
Expand Down