robotcodedev / robotcode

RobotFramework support for Visual Studio Code
https://robotcode.io
Apache License 2.0
177 stars 14 forks source link

[BUG] Arrays created in a `Variables` file cannot be passed as `Library` argument #174

Open marques-bruno opened 11 months ago

marques-bruno commented 11 months ago

Hi, I've noticed the following issue while using the extension:

Describe the bug When instantiating an argument-taking python library in a robot file (Library | CustomLib.py | ${Argument}), if ${Argument} is an array or a mapping, and if this array / mapping was declared in a Variables file (Variables | MyVars.py) the extension will fail to initialize the library.

To Reproduce Steps to reproduce the behavior: Create a simple project, with:

class CustomLib(object): ROBOT_LIBRARY_SCOPE = 'SUITE'

def __init__(self, VARIABLE, ANOTHER_VARIABLE, INTEGER, STRINGS, NUMBERS, MAPPING):
    self.variable = VARIABLE
    self.another_variable = ANOTHER_VARIABLE
    self.integer = INTEGER
    self.strings = STRINGS
    self.numbers = NUMBERS
    self.mapping = MAPPING

@keyword("Call foo")
def foo(self):
    logger.info(f"variable: {self.variable}")
    logger.info(f"another variable: {self.another_variable}")
    logger.info(f"integer {self.integer}")
    logger.info(f"strings {self.strings}")
    logger.info(f"numbers {self.numbers}")
    logger.info(f"mapping {self.mapping}")

- a robot file `test.robot`:
```robotframework
*** Settings ***
Variables      Variables.py
Library        CustomLib.py    ${VARIABLE}  ${ANOTHER_VARIABLE}  ${INTEGER}  ${STRINGS}  ${NUMBERS}  ${MAPPING}
# Library        CustomLib.py    ${VARIABLE}  ${ANOTHER_VARIABLE}  ${INTEGER}  ${STRINGLIST}  ${NUMBERLIST}  ${DICTIONARY}

*** Variables ***
@{STRINGLIST}    Hello    World
@{NUMBERLIST}    1    2    3
&{DICTIONARY}    one=1  two=2  three=3}

*** Test Cases ***
Test case
    Call foo

Expected behavior The test Test case will pass successfully, but the extension will produce an error when instantiating the library (Variable '${STRINGS}' not found. robotcode.namespace(VariableError)) Variables created within the robot file will not cause this issue (uncommenting l.4 and commenting l.3 in the robot file will show that the expect behavior works when the variables aren't declared in a dedicated Variables file). Unless of course the variable was constructed out of values coming from non-scalar values in the Variables.py

Screenshots image

d-biehl commented 11 months ago

Because of #125 I have limited the values I can import from a variable import for security and complexity reasons. Simple values are no problem, but for lists and dictionaries I haven't had time to find a reasonable solution yet. Issue #128 is to track the handling of dicts. But the behaviour you described is not tracked anywhere, so I leave this open. Personally I would think it works as expected, because of an unimplemented feature. So normally I would say that this is an improvement and not a bug. But I will leave this as a bug to emphasize how important it is.

Do you have a concrete problem you want to solve this way? Can you describe it? Maybe it will help me to find a good solution.

By the way, thanks for the beer ;-)

marques-bruno commented 11 months ago

Describing my issue helped me figuring out a workaround, and while in my case I think it could be helpful to have support for dicts from variables file, I understand that it might create performance issues in the extension.

So basically, I am testing a piece of embedded software using robotframework, and it requires a lot of configurable input / outputs (network sockets - host / port, COM ports with specific interfaces / baudrates / bitrates etc.) Each of these IOs are managed through keyword libraries, which I init with their respective config options from the .robot file. But all the config comes from a single toml file, which contains a dedicated section for each library basically.

So my idea was to have a variables file which would parse this file and expose all the sections as individual dicts which I would then pass to their respective libraries. Because some of these configuration options are also useful for testers, I wanted didn't want these options to be handled at the python level, but I wanted them to be exposed as robot variables. I'm reconsidering this choice, based on your comments though, and decided to go for the following solution: Each library accesses a singleton instance of a "config manager" from python, and gets the relevant config section, validates it and initializes itself with it. The library can still take "option overrides" through its init function, but these are individual options, not full dictionaries.

It works pretty well and I'm quite satisfied with the design after all :) Anyway, enjoy your coffee and thanks again ;)