6 from framework import VppTestCase
7 from multiprocessing import Process, Pipe
8 from pickle import dumps
12 if sys.version_info <= (3, 4):
13 from aenum import IntEnum
15 from enum import IntEnum
17 if sys.version_info <= (3, 6):
18 from aenum import IntFlag
20 from enum import IntFlag
23 class SerializableClassCopy(object):
25 Empty class used as a basis for a serializable copy of another class.
30 class RemoteClassAttr(object):
32 Wrapper around attribute of a remotely executed class.
35 def __init__(self, remote, attr):
36 self._path = [attr] if attr else []
39 def path_to_str(self):
40 return '.'.join(self._path)
42 def get_remote_value(self):
43 return self._remote._remote_exec(RemoteClass.GET, self.path_to_str())
46 return self._remote._remote_exec(RemoteClass.REPR, self.path_to_str())
49 return self._remote._remote_exec(RemoteClass.STR, self.path_to_str())
51 def __getattr__(self, attr):
53 if not (attr.startswith('__') and attr.endswith('__')):
55 self._path.append(attr)
58 def __setattr__(self, attr, val):
60 if not (attr.startswith('__') and attr.endswith('__')):
61 super(RemoteClassAttr, self).__setattr__(attr, val)
63 self._path.append(attr)
64 self._remote._remote_exec(RemoteClass.SETATTR, self.path_to_str(),
67 def __call__(self, *args, **kwargs):
68 return self._remote._remote_exec(RemoteClass.CALL, self.path_to_str(),
69 True, *args, **kwargs)
72 class RemoteClass(Process):
74 This class can wrap around and adapt the interface of another class,
75 and then delegate its execution to a newly forked child process.
77 # Create a remotely executed instance of MyClass
78 object = RemoteClass(MyClass, arg1='foo', arg2='bar')
80 # Access the object normally as if it was an instance of your class.
81 object.my_attribute = 20
82 print object.my_attribute
83 print object.my_method(object.my_attribute)
84 object.my_attribute.nested_attribute = 'test'
85 # If you need the value of a remote attribute, use .get_remote_value
86 method. This method is automatically called when needed in the context
87 of a remotely executed class. E.g.:
88 if (object.my_attribute.get_remote_value() > 20):
89 object.my_attribute2 = object.my_attribute
90 # Destroy the instance
95 GET = 0 # Get attribute remotely
96 CALL = 1 # Call method remotely
97 SETATTR = 2 # Set attribute remotely
98 REPR = 3 # Get representation of a remote object
99 STR = 4 # Get string representation of a remote object
100 QUIT = 5 # Quit remote execution
102 PIPE_PARENT = 0 # Parent end of the pipe
103 PIPE_CHILD = 1 # Child end of the pipe
105 DEFAULT_TIMEOUT = 2 # default timeout for an operation to execute
107 def __init__(self, cls, *args, **kwargs):
108 super(RemoteClass, self).__init__()
111 self._kwargs = kwargs
112 self._timeout = RemoteClass.DEFAULT_TIMEOUT
113 self._pipe = Pipe() # pipe for input/output arguments
116 return moves.reprlib.repr(RemoteClassAttr(self, None))
119 return str(RemoteClassAttr(self, None))
121 def __call__(self, *args, **kwargs):
122 return self.RemoteClassAttr(self, None)()
124 def __getattr__(self, attr):
125 if attr[0] == '_' or not self.is_alive():
126 if not (attr.startswith('__') and attr.endswith('__')):
127 if hasattr(super(RemoteClass, self), '__getattr__'):
128 return super(RemoteClass, self).__getattr__(attr)
130 return RemoteClassAttr(self, attr)
132 def __setattr__(self, attr, val):
133 if attr[0] == '_' or not self.is_alive():
134 if not (attr.startswith('__') and attr.endswith('__')):
135 super(RemoteClass, self).__setattr__(attr, val)
137 setattr(RemoteClassAttr(self, None), attr, val)
139 def _remote_exec(self, op, path=None, ret=True, *args, **kwargs):
141 Execute given operation on a given, possibly nested, member remotely.
143 # automatically resolve remote objects in the arguments
144 mutable_args = list(args)
145 for i, val in enumerate(mutable_args):
146 if isinstance(val, RemoteClass) or \
147 isinstance(val, RemoteClassAttr):
148 mutable_args[i] = val.get_remote_value()
149 args = tuple(mutable_args)
150 for key, val in six.iteritems(kwargs):
151 if isinstance(val, RemoteClass) or \
152 isinstance(val, RemoteClassAttr):
153 kwargs[key] = val.get_remote_value()
155 args = self._make_serializable(args)
156 kwargs = self._make_serializable(kwargs)
157 self._pipe[RemoteClass.PIPE_PARENT].send((op, path, args, kwargs))
159 # no return value expected
161 timeout = self._timeout
162 # adjust timeout specifically for the .sleep method
163 if path.split('.')[-1] == 'sleep':
164 if args and isinstance(args[0], (long, int)):
166 elif 'timeout' in kwargs:
167 timeout += kwargs['timeout']
168 if not self._pipe[RemoteClass.PIPE_PARENT].poll(timeout):
171 rv = self._pipe[RemoteClass.PIPE_PARENT].recv()
172 rv = self._deserialize(rv)
177 def _get_local_object(self, path):
179 Follow the path to obtain a reference on the addressed nested attribute
183 obj = getattr(obj, attr)
186 def _get_local_value(self, path):
188 return self._get_local_object(path)
189 except AttributeError:
192 def _call_local_method(self, path, *args, **kwargs):
194 method = self._get_local_object(path)
195 return method(*args, **kwargs)
196 except AttributeError:
199 def _set_local_attr(self, path, value):
201 obj = self._get_local_object(path[:-1])
202 setattr(obj, path[-1], value)
203 except AttributeError:
207 def _get_local_repr(self, path):
209 obj = self._get_local_object(path)
210 return moves.reprlib.repr(obj)
211 except AttributeError:
214 def _get_local_str(self, path):
216 obj = self._get_local_object(path)
218 except AttributeError:
221 def _serializable(self, obj):
222 """ Test if the given object is serializable """
229 def _make_obj_serializable(self, obj):
231 Make a serializable copy of an object.
232 Members which are difficult/impossible to serialize are stripped.
234 if self._serializable(obj):
235 return obj # already serializable
237 copy = SerializableClassCopy()
240 Dictionaries can hold complex values, so we split keys and values into
241 separate lists and serialize them individually.
243 if (type(obj) is dict):
244 copy.type = type(obj)
247 for k, v in obj.items():
248 copy.k_list.append(self._make_serializable(k))
249 copy.v_list.append(self._make_serializable(v))
252 # copy at least serializable attributes and properties
253 for name, member in inspect.getmembers(obj):
254 if name[0] == '_': # skip private members
255 if not (name.startswith('__') and name.endswith('__')):
257 if callable(member) and not isinstance(member, property):
259 if not self._serializable(member):
261 setattr(copy, name, member)
264 def _make_serializable(self, obj):
266 Make a serializable copy of an object or a list/tuple of objects.
267 Members which are difficult/impossible to serialize are stripped.
269 if (type(obj) is list) or (type(obj) is tuple):
272 rv.append(self._make_serializable(item))
273 if type(obj) is tuple:
276 elif (isinstance(obj, IntEnum) or isinstance(obj, IntFlag)):
279 return self._make_obj_serializable(obj)
281 def _deserialize_obj(self, obj):
282 if (hasattr(obj, 'type')):
285 for k, v in zip(obj.k_list, obj.v_list):
286 _obj[self._deserialize(k)] = self._deserialize(v)
290 def _deserialize(self, obj):
291 if (type(obj) is list) or (type(obj) is tuple):
294 rv.append(self._deserialize(item))
295 if type(obj) is tuple:
299 return self._deserialize_obj(obj)
301 def start_remote(self):
302 """ Start remote execution """
305 def quit_remote(self):
306 """ Quit remote execution """
307 self._remote_exec(RemoteClass.QUIT, None, False)
309 def get_remote_value(self):
310 """ Get value of a remotely held object """
311 return RemoteClassAttr(self, None).get_remote_value()
313 def set_request_timeout(self, timeout):
314 """ Change request timeout """
315 self._timeout = timeout
319 Create instance of the wrapped class and execute operations
320 on it as requested by the parent process.
322 self._instance = self._cls(*self._args, **self._kwargs)
326 # get request from the parent process
328 kwargs) = self._pipe[RemoteClass.PIPE_CHILD].recv()
329 args = self._deserialize(args)
330 kwargs = self._deserialize(kwargs)
331 path = path.split('.') if path else []
332 if op == RemoteClass.GET:
333 rv = self._get_local_value(path)
334 elif op == RemoteClass.CALL:
335 rv = self._call_local_method(path, *args, **kwargs)
336 elif op == RemoteClass.SETATTR and 'value' in kwargs:
337 self._set_local_attr(path, kwargs['value'])
338 elif op == RemoteClass.REPR:
339 rv = self._get_local_repr(path)
340 elif op == RemoteClass.STR:
341 rv = self._get_local_str(path)
342 elif op == RemoteClass.QUIT:
347 if not self._serializable(rv):
348 rv = self._make_serializable(rv)
349 self._pipe[RemoteClass.PIPE_CHILD].send(rv)
352 self._instance = None # destroy the instance
355 @unittest.skip("Remote Vpp Test Case Class")
356 class RemoteVppTestCase(VppTestCase):
357 """ Re-use VppTestCase to create remote VPP segment
363 # fork new process before clinet connects to VPP
364 cls.remote_test = RemoteClass(RemoteVppTestCase)
366 # start remote process
367 cls.remote_test.start_remote()
369 # set up your test case
370 super(MyTestCase, cls).setUpClass()
373 cls.remote_test.setUpClass(cls.tempdir)
376 def tearDownClass(cls):
377 # tear down remote test
378 cls.remote_test.tearDownClass()
380 # stop remote process
381 cls.remote_test.quit_remote()
383 # tear down your test case
384 super(MyTestCase, cls).tearDownClass()
388 super(RemoteVppTestCase, self).__init__("emptyTest")
390 # Note: __del__ is a 'Finalizer" not a 'Destructor'.
391 # https://docs.python.org/3/reference/datamodel.html#object.__del__
393 if hasattr(self, "vpp"):
395 if self.vpp.returncode is None:
397 self.vpp.communicate()
400 def setUpClass(cls, tempdir):
401 # disable features unsupported in remote VPP
402 orig_env = dict(os.environ)
403 if 'STEP' in os.environ:
404 del os.environ['STEP']
405 if 'DEBUG' in os.environ:
406 del os.environ['DEBUG']
407 cls.tempdir_prefix = os.path.basename(tempdir) + "/"
408 super(RemoteVppTestCase, cls).setUpClass()
409 os.environ = orig_env
411 @unittest.skip("Empty test")
416 def setTestFunctionInfo(self, name, doc):
418 Store the name and documentation string of currently executed test
419 in the main VPP for logging purposes.
421 self._testMethodName = name
422 self._testMethodDoc = doc