api: Cleanup APIs interface.api
[vpp.git] / test / remote_test.py
index dfdc630..9c825cc 100644 (file)
@@ -3,13 +3,13 @@
 import inspect
 import os
 import unittest
+from framework import VppTestCase
 from multiprocessing import Process, Pipe
 from pickle import dumps
-
 import six
 from six import moves
-
-from framework import VppTestCase
+import sys
+from aenum import IntEnum, IntFlag
 
 
 class SerializableClassCopy(object):
@@ -18,6 +18,9 @@ class SerializableClassCopy(object):
     """
     pass
 
+    def __repr__(self):
+        return '<SerializableClassCopy dict=%s>' % self.__dict__
+
 
 class RemoteClassAttr(object):
     """
@@ -42,21 +45,24 @@ 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):
@@ -113,18 +119,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.
         """
@@ -132,23 +140,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):
             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:
@@ -221,11 +226,30 @@ class RemoteClass(Process):
         """
         if self._serializable(obj):
             return obj  # already serializable
+
         copy = SerializableClassCopy()
+
+        """
+        Dictionaries can hold complex values, so we split keys and values into
+        separate lists and serialize them individually.
+        """
+        if (type(obj) is dict):
+            copy.type = type(obj)
+            copy.k_list = list()
+            copy.v_list = list()
+            for k, v in obj.items():
+                copy.k_list.append(self._make_serializable(k))
+                copy.v_list.append(self._make_serializable(v))
+            return copy
+
         # 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 not (name.startswith('__') and name.endswith('__')):
+                    continue
             if callable(member) and not isinstance(member, property):
                 continue
             if not self._serializable(member):
@@ -245,10 +269,18 @@ class RemoteClass(Process):
             if type(obj) is tuple:
                 rv = tuple(rv)
             return rv
+        elif (isinstance(obj, IntEnum) or isinstance(obj, IntFlag)):
+            return obj.value
         else:
             return self._make_obj_serializable(obj)
 
     def _deserialize_obj(self, obj):
+        if (hasattr(obj, 'type')):
+            if obj.type is dict:
+                _obj = dict()
+                for k, v in zip(obj.k_list, obj.v_list):
+                    _obj[self._deserialize(k)] = self._deserialize(v)
+            return _obj
         return obj
 
     def _deserialize(self, obj):
@@ -268,7 +300,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 """
@@ -324,7 +356,7 @@ class RemoteVppTestCase(VppTestCase):
 
         @classmethod
         def setUpClass(cls):
-            # fork new process before clinet connects to VPP
+            # fork new process before client connects to VPP
             cls.remote_test = RemoteClass(RemoteVppTestCase)
 
             # start remote process
@@ -351,12 +383,14 @@ class RemoteVppTestCase(VppTestCase):
     def __init__(self):
         super(RemoteVppTestCase, self).__init__("emptyTest")
 
+    # Note: __del__ is a 'Finalizer" not a 'Destructor'.
+    # https://docs.python.org/3/reference/datamodel.html#object.__del__
     def __del__(self):
         if hasattr(self, "vpp"):
-            cls.vpp.poll()
-            if cls.vpp.returncode is None:
-                cls.vpp.terminate()
-                cls.vpp.communicate()
+            self.vpp.poll()
+            if self.vpp.returncode is None:
+                self.vpp.terminate()
+                self.vpp.communicate()
 
     @classmethod
     def setUpClass(cls, tempdir):
@@ -370,6 +404,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 """