X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=test%2Fremote_test.py;h=19bad897cdf36b44b7de76d7bf8a6e8844a65b17;hb=0ad060851b4989a1cd300ad711e8498435f65f9a;hp=e90ccb24f830c82713312f9ab276b6271b2044f6;hpb=f70cead5eb928954908de48300cc7a9c88430c0f;p=vpp.git diff --git a/test/remote_test.py b/test/remote_test.py old mode 100644 new mode 100755 index e90ccb24f83..19bad897cdf --- a/test/remote_test.py +++ b/test/remote_test.py @@ -1,26 +1,28 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import inspect import os +import reprlib import unittest +from framework import VppTestCase from multiprocessing import Process, Pipe from pickle import dumps +import sys -import six -from six import moves - -from framework import VppTestCase -from enum import Enum +from enum import IntEnum, IntFlag -class SerializableClassCopy(object): +class SerializableClassCopy: """ Empty class used as a basis for a serializable copy of another class. """ pass + def __repr__(self): + return '' % self.__dict__ + -class RemoteClassAttr(object): +class RemoteClassAttr: """ Wrapper around attribute of a remotely executed class. """ @@ -43,44 +45,57 @@ class RemoteClassAttr(object): def __getattr__(self, attr): if attr[0] == '_': - raise AttributeError + if not (attr.startswith('__') and attr.endswith('__')): + raise AttributeError('tried to get private attribute: %s ', + attr) self._path.append(attr) return self def __setattr__(self, attr, val): if attr[0] == '_': - super(RemoteClassAttr, self).__setattr__(attr, val) - return + if not (attr.startswith('__') and attr.endswith('__')): + super(RemoteClassAttr, self).__setattr__(attr, val) + return self._path.append(attr) self._remote._remote_exec(RemoteClass.SETATTR, self.path_to_str(), - True, value=val) + value=val) def __call__(self, *args, **kwargs): return self._remote._remote_exec(RemoteClass.CALL, self.path_to_str(), - True, *args, **kwargs) + *args, **kwargs) class RemoteClass(Process): """ This class can wrap around and adapt the interface of another class, and then delegate its execution to a newly forked child process. + Usage: - # Create a remotely executed instance of MyClass - object = RemoteClass(MyClass, arg1='foo', arg2='bar') - object.start_remote() - # Access the object normally as if it was an instance of your class. - object.my_attribute = 20 - print object.my_attribute - print object.my_method(object.my_attribute) - object.my_attribute.nested_attribute = 'test' - # If you need the value of a remote attribute, use .get_remote_value - method. This method is automatically called when needed in the context - of a remotely executed class. E.g.: - if (object.my_attribute.get_remote_value() > 20): - object.my_attribute2 = object.my_attribute - # Destroy the instance - object.quit_remote() - object.terminate() + + #. Create a remotely executed instance of MyClass. :: + + object = RemoteClass(MyClass, arg1='foo', arg2='bar') + object.start_remote() + + #. Access the object normally as if it was an instance of your + class. :: + + object.my_attribute = 20 + print object.my_attribute + print object.my_method(object.my_attribute) + object.my_attribute.nested_attribute = 'test' + + #. If you need the value of a remote attribute, use .get_remote_value + method. This method is automatically called when needed in the + context of a remotely executed class. E.g. :: + + if (object.my_attribute.get_remote_value() > 20): + object.my_attribute2 = object.my_attribute + + #. Destroy the instance. :: + + object.quit_remote() + object.terminate() """ GET = 0 # Get attribute remotely @@ -104,7 +119,7 @@ class RemoteClass(Process): self._pipe = Pipe() # pipe for input/output arguments def __repr__(self): - return moves.reprlib.repr(RemoteClassAttr(self, None)) + return reprlib.repr(RemoteClassAttr(self, None)) def __str__(self): return str(RemoteClassAttr(self, None)) @@ -114,18 +129,20 @@ class RemoteClass(Process): def __getattr__(self, attr): if attr[0] == '_' or not self.is_alive(): - if hasattr(super(RemoteClass, self), '__getattr__'): - return super(RemoteClass, self).__getattr__(attr) - raise AttributeError + if not (attr.startswith('__') and attr.endswith('__')): + if hasattr(super(RemoteClass, self), '__getattr__'): + return super(RemoteClass, self).__getattr__(attr) + raise AttributeError('missing: %s', attr) return RemoteClassAttr(self, attr) def __setattr__(self, attr, val): if attr[0] == '_' or not self.is_alive(): - super(RemoteClass, self).__setattr__(attr, val) - return + if not (attr.startswith('__') and attr.endswith('__')): + super(RemoteClass, self).__setattr__(attr, val) + return setattr(RemoteClassAttr(self, None), attr, val) - def _remote_exec(self, op, path=None, ret=True, *args, **kwargs): + def _remote_exec(self, op, path=None, *args, **kwargs): """ Execute given operation on a given, possibly nested, member remotely. """ @@ -133,23 +150,20 @@ class RemoteClass(Process): mutable_args = list(args) for i, val in enumerate(mutable_args): if isinstance(val, RemoteClass) or \ - isinstance(val, RemoteClassAttr): + isinstance(val, RemoteClassAttr): mutable_args[i] = val.get_remote_value() args = tuple(mutable_args) - for key, val in six.iteritems(kwargs): + for key, val in kwargs.items(): if isinstance(val, RemoteClass) or \ - isinstance(val, RemoteClassAttr): + isinstance(val, RemoteClassAttr): kwargs[key] = val.get_remote_value() # send request args = self._make_serializable(args) kwargs = self._make_serializable(kwargs) self._pipe[RemoteClass.PIPE_PARENT].send((op, path, args, kwargs)) - if not ret: - # no return value expected - return None timeout = self._timeout # adjust timeout specifically for the .sleep method - if path.split('.')[-1] == 'sleep': + if path is not None and path.split('.')[-1] == 'sleep': if args and isinstance(args[0], (long, int)): timeout += args[0] elif 'timeout' in kwargs: @@ -196,7 +210,7 @@ class RemoteClass(Process): def _get_local_repr(self, path): try: obj = self._get_local_object(path) - return moves.reprlib.repr(obj) + return reprlib.repr(obj) except AttributeError: return None @@ -240,12 +254,18 @@ class RemoteClass(Process): # copy at least serializable attributes and properties for name, member in inspect.getmembers(obj): - if name[0] == '_': # skip private members - continue + # skip private members and non-writable dunder methods. + if name[0] == '_': + if name in ['__weakref__']: + continue + if name in ['__dict__']: + continue + if not (name.startswith('__') and name.endswith('__')): + continue if callable(member) and not isinstance(member, property): continue if not self._serializable(member): - continue + member = self._make_serializable(member) setattr(copy, name, member) return copy @@ -261,7 +281,7 @@ class RemoteClass(Process): if type(obj) is tuple: rv = tuple(rv) return rv - elif (isinstance(obj, Enum)): + elif (isinstance(obj, IntEnum) or isinstance(obj, IntFlag)): return obj.value else: return self._make_obj_serializable(obj) @@ -292,7 +312,7 @@ class RemoteClass(Process): def quit_remote(self): """ Quit remote execution """ - self._remote_exec(RemoteClass.QUIT, None, False) + self._remote_exec(RemoteClass.QUIT, None) def get_remote_value(self): """ Get value of a remotely held object """ @@ -344,32 +364,32 @@ class RemoteClass(Process): class RemoteVppTestCase(VppTestCase): """ Re-use VppTestCase to create remote VPP segment - In your test case: + In your test case:: - @classmethod - def setUpClass(cls): - # fork new process before clinet connects to VPP - cls.remote_test = RemoteClass(RemoteVppTestCase) + @classmethod + def setUpClass(cls): + # fork new process before client connects to VPP + cls.remote_test = RemoteClass(RemoteVppTestCase) - # start remote process - cls.remote_test.start_remote() + # start remote process + cls.remote_test.start_remote() - # set up your test case - super(MyTestCase, cls).setUpClass() + # set up your test case + super(MyTestCase, cls).setUpClass() - # set up remote test - cls.remote_test.setUpClass(cls.tempdir) + # set up remote test + cls.remote_test.setUpClass(cls.tempdir) - @classmethod - def tearDownClass(cls): - # tear down remote test - cls.remote_test.tearDownClass() + @classmethod + def tearDownClass(cls): + # tear down remote test + cls.remote_test.tearDownClass() - # stop remote process - cls.remote_test.quit_remote() + # stop remote process + cls.remote_test.quit_remote() - # tear down your test case - super(MyTestCase, cls).tearDownClass() + # tear down your test case + super(MyTestCase, cls).tearDownClass() """ def __init__(self): @@ -396,6 +416,10 @@ class RemoteVppTestCase(VppTestCase): super(RemoteVppTestCase, cls).setUpClass() os.environ = orig_env + @classmethod + def tearDownClass(cls): + super(RemoteVppTestCase, cls).tearDownClass() + @unittest.skip("Empty test") def emptyTest(self): """ Do nothing """