Log input and output on a testing framework that uses Python, Pyro and pytest

310 Views Asked by At

I think it’s best if I explain what I want to do, first: I am currently developing a testing framework using Python, pytest and Pyro4. It is indented to test software running on hardware prototypes. The developer/tester is executing the test script on his PC locally using pytest. In the test script, actions are executed using Pyro4 proxy objects which connect to a Pyro4 server (daemon) running on a Raspberry Pi. The implementation on the Raspberry Pi adapts various sensors and relays that are connected to the device under test, represented as objects, which are then passed to the Pyro4 daemon.

Here is a simplified example of how a test script could look like. What it does: Press button x and measure if LED y lights up.

@pytest.fixture(scope="module")
def test_components():
    # initialize a dict of pyro proxy objects that represent a hardware component, controlled by the Raspberry Pi
    a_dict = {}
    a_dict['button_x'] = Pyro4.Proxy("PYRO:[email protected]:1234")
    a_dict['status_led'] = Pyro4.Proxy("PYRO:[email protected]:1234")
    return a_dict

def test_functionality_button_led(test_components):
    test_components['button_x'].set_for(0.5)    # presses button x / enables relay x for 0.5 seconds
    time.sleep(1)
    assert test_compontents['status_led'].get_level() > 0    # assert that led is on

Now, to my initial question: After each test run, i want to have a log file that shows the function calls, their input values and their return values. So it should look roughly like this:

0.1:    test_components('button_x').set_for(0.5) [None]
0.2:    time.sleep(1) [None]
1.2:    assert test_compontents('status_led').get_level() [1] > 0

I had two options in mind but was not able to archieve this:

Leveraging pytest

I was hoping to find some switch or plugin (for example pytest-logging) which would help me do this, but i simply could not find anything to archieve this.

Leveraging Pyro

This is where i got a bit farer. I was hoping to be able to subclass Pyro4 Proxy (the client) stuff to get this done. I subclassed Pyro4.Proxy and reimplemented the __getattr__ method, which is called to get the methods from the remote object at runtime. Using the name attribute, i can now at least detect when and which method is called, but i can still not get the return and input values.

class ImprovedProxy(Pyro4.Proxy):

    def __getattr__(self, name):

        print(name)
        return = Pyro4.Proxy.__getattr__(self, name)

To get the input and output values i would need to subclass and replace the _RemoteMethod class, which is returned by Pyro4.Proxy.__getattr__(self, name) but i couldn't even run the original code from Pyro4.Proxy.__getattr__(self, name) in my ImprovedProxy.__getattr__(self, name) without loads of errors, see for yourself:

class ImprovedProxy(Pyro4.Proxy):


    def __getattr__(self, name):
        if name in Pyro4.Proxy.__pyroAttributes:
            # allows it to be safely pickled
            raise AttributeError(name)
        if config.METADATA:
            # get metadata if it's not there yet
            if not self._pyroMethods and not self._pyroAttrs:
                self._pyroGetMetadata()
        if name in self._pyroAttrs:
            return self._pyroInvoke("__getattr__", (name,), None)
        if config.METADATA and name not in self._pyroMethods:
            # client side check if the requested attr actually exists
            raise AttributeError("remote object '%s' has no exposed attribute or method '%s'" % (self._pyroUri, name))
        if self.__async:
            return _AsyncRemoteMethod(self, name, self._pyroMaxRetries)
        return _RemoteMethod(self._pyroInvoke, name, self._pyroMaxRetries)

The Traceback was:

  File "C:\...\client.py", line 38, in __getattr__
    if name in Pyro4.Proxy.__pyroAttributes:
AttributeError: type object 'Proxy' has no attribute '_ImprovedProxy__pyroAttributes'

Not sure why the name of my subclass is prepended here and after fiddling with the code for a while, i am not sure if this is the right way.

Is there a better way to solve this? As a last resort i could log actions on the server and send it to the client, but i would like to avoid this, one reason being that i have multiple of these test adapters running which would then require precise time syncronisation.

I hope i could explain this well enought. Looking forward to your replies.

1

There are 1 best solutions below

2
On BEST ANSWER

The reason you're seeing '_ImprovedProxy__pyroAttributes' in your traceback is because your code is trying to access a private attribute on the base proxy. First line in your __getattr__ method. Python is doing its standard name mangling, like it does for all attributes starting with a double underscore, to make it a "private" attribute. You are not supposed to access this attribute.

Now, the actual solution to your issue is, however, simple, I think: override the proxy's _pyroInvoke method instead. This is what eventually gets called, and it is the logic that is doing the actual remote method call. I think it provides all the arguments that you need:

def _pyroInvoke(self, methodname, vargs, kwargs, flags=0, objectId=None):
    """perform the remote method call communication"""
    ...