Arguments provided to Remote Library Interface do not follow XML-RPC specification

I’ve spent quite a bit of work to implement the remote library interface properly in a .NET application, only to find out, that robot framework does not properly convert their lists and dictionaries to arrays and key/value pairs. At least that’s my current understanding of the situation, please enlighten me if I’m wrong.

I have this set of variables configured:
# Arrays and lists are the same thing in python robot framework.
@{LIST_INT32} ${1} ${2} ${3}
@{LIST_BOOL} ${True} ${False} ${True}
@{LIST_DOUBLE} ${1.0} ${0.5} ${-0.5}
@{LIST_STRING} Abc $def GHI

&{DICTIONARY_INT32}     x=${1}    y=${2}    z=${3}
&{DICTIONARY_BOOL}      x=${True}    y=${False}    z=${True}
&{DICTIONARY_DOUBLE}    x=${1.0}    y=${0.5}    z=${-0.5}
&{DICTIONARY_STRING}    x=Abc    y=$def    z=GHI

I am then calling my test like so: (showing only 1 for simplicity)
List Int32 Conversion
${converted_list}= TESTLIB.ListInt32 ParameterType ${LIST_INT32}
Lists Should Be Equal ${converted_list} ${LIST_INT32}

When this keyword is executed, I receive this on my remote library interface:
grafik

This is not what I would expect, given that the type configured is a List, but maybe I’m doing something wrong in my initialization.

When using @{LIST_INT32} instead, the values get transmitted properly as integers, but now they are expanded on separate arguments and the call fails prematurely, since my keyword only has 1 argument.

I built a python class library and I’ve sent the object directly with:
result = self.remote_lib.run_keyword(func_name, data, None)

And this results in an object with the correct element types. That’s something to work with at least.

But I don’t want to build a class library, only to run the remote keyword in native python, it kinda defeats the purpose.

Can somebody tell me how I can send ${variable} to a remote library keyword and have it behave as expected?

It seems I’ve made a bunch of mistakes in formatting. I am sorry, I can’t edit the post apparantly. Hope you can read it.

As comparison, I would expect the XML-RPC client of robot framework to either send object arrays:
grafik

Or to send the more formal type arrays which are also supported:
grafik

I still need to make a representative example using the python xml-rpc client, as the above examples are sent from my unit-tests with a client proxy.

I checked the code in https://github.com/robotframework/robotframework/blob/master/src/robot/libraries/Remote.py and also built a simple python example using the xml-rpc.client.

I now suspect, that the problem lies with the way I’m setting up these variables in robot framework, since the python client behaves as expected and also robot frameworks client is setup the same way.

So can anyone tell me, how to properly pass a list or dictionary to a remote server?

I actually figured out a little problem in my remote server logic, which was handling single argument keywords badly, if they have an array, since XML-RPC behaves differently then.

The key problem of this issue is the fact, that Robot Framework sends the list AS A STRING! instead of the desired list object.

1 Like

Does anyone know how I can pass a dictionary or list variable as an object instead of its string representation?

Hi @HackXIt,

That’s all covered in Using variables

  • ${Scalar_Variable}
  • @{List_Variable}
  • &{Dictionary_Variable}

Dave.

I have read that part of the documentation carefully, the problem is, none of these work.

Using $ for scalar variables, it provides the string representation of the object, instead of the actual list or dictionary.
Using @ or & it expands the object, providing more arguments, than the keyword actually expects, which definitely fails.

As described in the issue, I have used ${LIST_INT32} to pass the variable containing the list, but all I receive on the server is a string like this: “[1, 2, 3]”
I do not intend to build implement or use a parser for strings, when XMLRPC supports these object types natively.

This is what the documentation states:

The most common way to use variables in Robot Framework test data is using the scalar variable syntax like ${var}. When this syntax is used, the variable name is replaced with its value as-is. Most of the time variable values are strings, but variables can contain any object, including numbers, lists, dictionaries, or even custom objects.

The example below illustrates the usage of scalar variables. Assuming that the variables ${GREET} and ${NAME} are available and assigned to strings Hello and world, respectively, both the example test cases are equivalent.

*** Test Cases ***
Constants
    Log    Hello
    Log    Hello, world!!

Variables
    Log    ${GREET}
    Log    ${GREET}, ${NAME}!!

When a scalar variable is used alone without any text or other variables around it, like in ${GREET} above, the variable is replaced with its value as-is and the value can be any object. If the variable is not used alone, like ${GREER}, ${NAME}!! above, its value is first converted into a string and then concatenated with the other data.

So according to this, my usage of the remote library, should provide the object as-is:

List Int32 Conversion
    TESTLIB.ListInt32 ParameterType    ${LIST_INT32} # Simple test ignoring return value
    ${converted_list}=    TESTLIB.ListInt32 ParameterType    ${LIST_INT32} # Test with return value
    Lists Should Be Equal    ${converted_list}    ${LIST_INT32} # Compare return value with argument

But instead, I receive a string, which produces this fault on my remote library server:

Unable to cast object=[1, 2, 3] (typeSystem.String) for List<> conversion.

When hopping into the debugger, I clearly see, that the provided argument is a string, and it would fail execution, since all arguments are type checked.

When attempting to replicate this with simple python xml rpc client I get different results:

def execute_keyword(uri, keyword, *args):
    with xmlrpc.client.ServerProxy(uri, encoding='UTF-8', use_builtin_types=True, verbose=True) as proxy:
        print(proxy)
        try:
            print(proxy.run_keyword(keyword, *args))
        except xmlrpc.client.Fault as err:
            print(err)

func_name = f'ListInt32 ParameterType'
execute_keyword('http://localhost:8270/Testcenter/RobotFramework/Test/KeywordLibrary/TestKeywords', func_name, [intList])

It returns the expected result (the executed keyword):

<ServerProxy for localhost:8270/Testcenter/RobotFramework/Test/KeywordLibrary/TestKeywords>
send: b'POST /Testcenter/RobotFramework/Test/KeywordLibrary/TestKeywords HTTP/1.1
Host: localhost:8270
Accept-Encoding: gzip
Content-Type: text/xml
User-Agent: Python-xmlrpc/3.11
Content-Length: 442

'
send: b"<?xml version='1.0' encoding='UTF-8'?>\n<methodCall>\n<methodName>run_keyword</methodName>\n<params>\n<param>\n<value><string>ListInt32 ParameterType</string></value>\n</param>\n<param>\n<value><array><data>\n<value><array><data>\n<value><int>1</int></value>\n<value><int>2</int></value>\n<value><int>3</int></value>\n<value><int>4</int></value>\n<value><int>5</int></value>\n</data></array></value>\n</data></array></value>\n</param>\n</params>\n</methodCall>\n"
reply: 'HTTP/1.1 200 OK
'
header: Content-Length: 1593
header: Content-Type: text/xml
header: Server: Microsoft-HTTPAPI/2.0      
header: Date: Tue, 19 Sep 2023 09:13:31 GMT
body: b'<?xml version="1.0"?>
<methodResponse>
  <params>
    <param>
      <value>
        <struct>
          <member>
            <name>status</name>
   
         <value>
              <string>PASS</string>
            </value>
          </member>
          <member>
            <name>error</name>
            <value>
              <string />
            </value>
          </member>
          <member>
            <name>traceback</name>
            <value>
              <string />
            </value>
          </member>
          <member>
            <name>output</name>
            <value>
              <string>*DEBUG:1695114811790* KeywordManager Keyword invocation: ListInt32_ParameterType(list=System.Collections.Generic.List`1[System.Int32]) ...
[0]=1
[1]=2
[2]=3
[3]=4
[4]=5
</string>
            </value>
          </member>
          <member>
            <name>return</name>
            <value>
              <array>
                <data>
                  <value>
          '
body: b'          <i4>1</i4>
                  </value>
                  <value>
                    <i4>2</i4>
                  </value>
                  <value>
                    <i4>3</i4>
                  </value>
                  <value>
                    <i4>4</i4>
                  </value>

 <value>
                    <i4>5</i4>
                  </value>
                </data>
              </array>
            </value>
          </member>
   
     </struct>
      </value>
    </param>
  </params>
</methodResponse>'
{'status': 'PASS', 'error': '', 'traceback': '', 'output': '*DEBUG:1695114811790* KeywordManager Keyword invocation: ListInt32_ParameterType(list=System.Collections.Generic.List`1[System.Int32]) ...\n[0]=1\n[1]=2\n[2]=3\n[3]=4\n[4]=5\n', 'return': [1, 2, 3, 4, 5]}

Hi @HackXIt,

I’ve never used XML-RPC, that’s why I didn’t reply earlier than today.

I’m beginning to suspect the issue you’re having with the list being converted to a string might be happening outside robot framework, which might explain the lack of help you are getting here.

And perhaps the conversion to string is something intentional that happens in the XML-RPC Library for compliance with some standard? so it might be up to your .NET library to convert it back to the expected object type? But this is beyond my knowledge.

I knocked up a simple example for you to show you objects being passed to a python library, you can recreate this example and make sure you can reproduce my results to confirm robot framework is working as expected, and hopefully that will help you narrow down where the problem is happening.

Dave.

Filename: HackXIt.py
Contents:

class HackXIt:
	def check_var_type(self, var):
		print("Type:", type(var), "	Value:", var)

	def check_var_list(self, *vars):
		print("Type:", type(vars), "	Value:", vars)
		for var in vars:
			print("Type:", type(var), "	Value:", var)

Filename: HackXIt.robot
Contents:

*** Settings ***
Library 	HackXIt.py

*** Variables ***
@{LIST_INT32}    ${1}    ${2}    ${3}
@{LIST_BOOL}    ${True}    ${False}    ${True}
@{LIST_DOUBLE}    ${1.0}    ${0.5}    ${-0.5}
@{LIST_STRING}    Abc    $def    GHI

&{DICTIONARY_INT32}     x=${1}    y=${2}    z=${3}
&{DICTIONARY_BOOL}      x=${True}    y=${False}    z=${True}
&{DICTIONARY_DOUBLE}    x=${1.0}    y=${0.5}    z=${-0.5}
&{DICTIONARY_STRING}    x=Abc    y=$def    z=GHI

*** Test Cases ***
List Example
	Check Var Type    ${LIST_BOOL}

List Example 2
	Check Var List    @{LIST_BOOL}

Dictionary Example
	Check Var Type    ${DICTIONARY_STRING}

Result:

I’ve never used XML-RPC, that’s why I didn’t reply earlier than today.

The remote library class of robot framework uses XML-RPC internally, more specifically this: xmlrpc.client — XML-RPC client access — Python 3.12.0 documentation

I’m beginning to suspect the issue you’re having with the list being converted to a string might be happening outside robot framework, which might explain the lack of help you are getting here.

It must happen inside of robot framework, since it is responsible for passing arguments to the remote library. I have verified, that if I pass from the above xml-rpc client directly (replicating the python native) that it works.
More specifically, this part of the robot framework code contains everything there is about the xml-rpc client:

In line 245 it passes the arguments provided to the xml-rpc client. That client is responsible for converting the provided objects into a seralized format.

And perhaps the conversion to string is something intentional that happens in the XML-RPC Library for compliance with some standard? so it might be up to your .NET library to convert it back to the expected object type? But this is beyond my knowledge.

As demonstrated, the native behavior of the python xml-rpc client is to convert lists or dictionaries into arrays or structs.

I knocked up a simple example for you to show you objects being passed to a python library, you can recreate this example and make sure you can reproduce my results to confirm robot framework is working as expected, and hopefully that will help you narrow down where the problem is happening.

Thank you, I’ll take a look into that, but I do want to mention it’s a bad example for comparison, since a difference class is responsible for loading keywords from a python class/module.
So the behavior is inherently different from when I initialize a remote library.

I also want to mention it is not possible to see my problem in this case, because type() directly prints the type and using any variable in a string, it automatically converts the object to string.

For me the key problem is the missing conversion from a python object to the corresponding XmlRpc format and the reason for that is the fact, that the arguments provided to the Remote library class are somehow either converted to a string before or something else is happening.

Either way, the XML-RPC client uses a string for serialization instead of the list or dictionary object.

Created an issue for this here:

1 Like

Hi @HackXIt

I’ll suggest you add a link to this forum thread to the issue, just so they know what’s already been tried, might save some repetition.

Dave.

Issue is resolved, as written in the github issue.

1 Like