-#!/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 '<SerializableClassCopy dict=%s>' % self.__dict__
+
-class RemoteClassAttr(object):
+class RemoteClassAttr:
"""
Wrapper around attribute of a remotely executed class.
"""
def __getattr__(self, attr):
if attr[0] == '_':
if not (attr.startswith('__') and attr.endswith('__')):
- raise AttributeError
+ raise AttributeError('tried to get private attribute: %s ',
+ attr)
self._path.append(attr)
return self
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
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))
if not (attr.startswith('__') and attr.endswith('__')):
if hasattr(super(RemoteClass, self), '__getattr__'):
return super(RemoteClass, self).__getattr__(attr)
- raise AttributeError
+ raise AttributeError('missing: %s', attr)
return RemoteClassAttr(self, attr)
def __setattr__(self, 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.
"""
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:
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
# copy at least serializable attributes and properties
for name, member in inspect.getmembers(obj):
- if name[0] == '_': # skip private members
+ # 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
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)
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 """
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):
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 """