Skip to content

Commit

Permalink
Expanded testing to validate BaseNetref/NetrefMetaclass __mro__ since…
Browse files Browse the repository at this point in the history
… any a fix for #355 and #547 will be a break-change/major-release.
  • Loading branch information
comrumino committed Jan 10, 2024
1 parent c3ff92b commit f715399
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 32 deletions.
11 changes: 6 additions & 5 deletions rpyc/core/netref.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import sys
import types
from rpyc.lib import get_methods, get_id_pack
from rpyc.lib.compat import pickle, maxint, with_metaclass
from rpyc.lib.compat import pickle, maxint
from rpyc.core import consts


Expand Down Expand Up @@ -83,7 +83,6 @@ class NetrefMetaclass(type):
"""A *metaclass* used to customize the ``__repr__`` of ``netref`` classes.
It is quite useless, but it makes debugging and interactive programming
easier"""

__slots__ = ()

def __repr__(self):
Expand All @@ -93,7 +92,7 @@ def __repr__(self):
return f"<netref class '{self.__name__}'>"


class BaseNetref(with_metaclass(NetrefMetaclass, object)):
class BaseNetref(object, metaclass=NetrefMetaclass):
"""The base netref class, from which all netref classes derive. Some netref
classes are "pre-generated" and cached upon importing this module (those
defined in the :data:`_builtin_types`), and they are shared between all
Expand Down Expand Up @@ -320,16 +319,18 @@ def class_factory(id_pack, methods):
_class = getattr(_module, _class_name, None)
if _class is not None and hasattr(_class, '__class__'):
class_descriptor = NetrefClass(_class)
elif _class is None:
class_descriptor = NetrefClass(type(_module))
break
ns['__class__'] = class_descriptor
netref_name = class_descriptor.owner.__name__ if class_descriptor is not None else name_pack
# create methods that must perform a syncreq
for name, doc in methods:
name = str(name) # IronPython issue #10
# only create methods that won't shadow BaseNetref during merge for mro
if name not in LOCAL_ATTRS: # i.e. `name != __class__`
ns[name] = _make_method(name, doc)
return type(netref_name, (BaseNetref,), ns)
netref_cls = type(name_pack, (BaseNetref, ), ns)
return netref_cls


for _builtin in _builtin_types:
Expand Down
97 changes: 70 additions & 27 deletions tests/test_netref_hierachy.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import inspect
import math
import rpyc
from rpyc.utils.server import ThreadedServer
from rpyc import SlaveService
from rpyc.core import netref
import unittest


logger = rpyc.lib.setup_logger()


class MyMeta(type):

def spam(self):
Expand Down Expand Up @@ -45,11 +50,44 @@ def exposed_instance(self, inst, cls):

def exposed_getnonetype(self):
""" About the unit test - what's common to types.MethodType and NoneType is that both are
not accessible via builtins. So the unit test I've added in 108ff8e was enough to
not accessible via builtins. So the unit test I've added in 108ff8e was enough to
my understanding (implement it with NoneType because that's more easily "created") """
return type(None)


class TestBaseNetrefMRO(unittest.TestCase):
def setUp(self):
self.conn = rpyc.classic.connect_thread()

def tearDown(self):
self.conn.close()
self.conn = None

def test_mro(self):
# TODO: netref.class_factory, redesign to register builtin types and better handle generic-aliases/types
# - components to explore: abc.ABCMeta, abc.ABC.register types
# - add mro test for netrefs to remote builtins
self.assertEqual(netref.NetrefMetaclass.__mro__, (netref.NetrefMetaclass, type, object))

def test_basenetref(self):
self.assertIsInstance(netref.BaseNetref, netref.NetrefMetaclass)
self.assertIsInstance(netref.BaseNetref, object)
mro = inspect.getmro(netref.BaseNetref)
self.assertEqual(mro, (netref.BaseNetref, object))

def test_builtins_dict_netref(self):
cls = netref.builtin_classes_cache['builtins.dict']
mro_netref = inspect.getmro(cls)
mro_dict = inspect.getmro(dict)
logger.debug('\n')
logger.debug(f'dict_netref: {mro_netref}')
logger.debug(f'dict: {mro_dict}')
self.conn.execute("dict_ = dict(a=0xd35db33f)")
remote_dict = self.conn.namespace['dict_']
logger.debug(f'remote_dict: {remote_dict}')
self.assertEqual(remote_dict['a'], 3546133311)


class Test_Netref_Hierarchy(unittest.TestCase):

@classmethod
Expand All @@ -60,16 +98,13 @@ def setUpClass(cls):

def setUp(self):
self.conn = rpyc.classic.connect('localhost', port=18878)
self.conn2 = None

@classmethod
def tearDownClass(cls):
cls.server.close()

def tearDown(self):
self.conn.close()
if self.conn2 is not None:
self.conn2.close()

def test_instancecheck_across_connections(self):
self.conn2 = rpyc.classic.connect('localhost', port=18878)
Expand All @@ -91,9 +126,16 @@ def test_classic(self):
isinstance([], x)
i = 0
self.assertTrue(type(x).__getitem__(x, i) == x.__getitem__(i))
_builtins = self.conn.modules.builtins if rpyc.lib.compat.is_py_3k else self.conn.modules.__builtin__
self.assertEqual(repr(_builtins.float.__class__), repr(type))
self.assertEqual(repr(type(_builtins.float)), repr(type(_builtins.type)))

def test_builtins(self):
_builtins = self.conn.modules.builtins
self.assertEqual(repr(_builtins.dict), repr(dict)) # Check repr behavior of netref matchs local

Check failure on line 132 in tests/test_netref_hierachy.py

View workflow job for this annotation

GitHub Actions / Check for spelling errors

matchs ==> matches
self.assertEqual(repr(type(_builtins.dict.__class__)), repr(type)) # Check netref __class__ is type
self.assertIs(type(_builtins.dict.__class__), type)
# Check class descriptor for netrefs
dict_ = _builtins.dict(space='remote')
self.assertIs(type(dict_).__dict__['__class__'].instance, dict)
self.assertIs(type(dict_).__dict__['__class__'].owner, type)

def test_instancecheck_list(self):
service = MyService()
Expand Down Expand Up @@ -122,27 +164,28 @@ def test_StandardError(self):

def test_modules(self):
"""
>>> type(sys)
<type 'module'> # base case
>>> type(conn.modules.sys)
<netref class 'rpyc.core.netref.__builtin__.module'> # matches base case
>>> sys.__class__
<type 'module'> # base case
>>> conn.modules.sys.__class__
<type 'module'> # matches base case
>>> type(sys.__class__)
<type 'type'> # base case
>>> type(conn.modules.sys.__class__)
<netref class 'rpyc.core.netref.__builtin__.module'> # doesn't match.
# ^Should be a netref class of "type" (or maybe just <type 'type'> itself?)
>>> type(unittest)
<class 'module'>
>>> type(self.conn.modules.unittest)
<netref class 'rpyc.core.netref.unittest'> # reflects that it is a proxy object to unittest
>>> unittest.__class__
<class 'module'> # base case
>>> conn.modules.unittest.__class__
<class 'module'> # matches base case
>>> type(unittest.__class__)
<class 'type'> # base case
>>> type(conn.modules.unittest.__class__)
<class 'type'> # matches base case
"""
import sys
self.assertEqual(repr(sys.__class__), repr(self.conn.modules.sys.__class__))
# _builtin = sys.modules['builtins' if rpyc.lib.compat.is_py_3k else '__builtins__'].__name__
# self.assertEqual(repr(type(self.conn.modules.sys)),
# "<netref class 'rpyc.core.netref.{}.module'>".format(_builtin))
# self.assertEqual(repr(type(self.conn.modules.sys.__class__)),
# "<netref class 'rpyc.core.netref.{}.type'>".format(_builtin))
self.assertEqual(repr(self.conn.modules.unittest), repr(unittest))
self.assertEqual(repr(type(self.conn.modules.unittest)), "<netref class 'rpyc.core.netref.unittest'>")
self.assertIs(self.conn.modules.unittest.__class__, type(unittest))
self.assertIs(type(self.conn.modules.unittest.__class__), type)

def test_proxy_instancecheck(self):
self.assertIsInstance(self.conn.modules.builtins.RuntimeError(), Exception)
# TODO: below should pass
# self.assertIsInstance(self.conn.modules.builtins.RuntimeError(), self.conn.modules.builtins.Exception)


if __name__ == '__main__':
Expand Down
42 changes: 42 additions & 0 deletions tests/test_urllib3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import rpyc
import inspect
from rpyc.utils.server import ThreadedServer
from rpyc import SlaveService
import unittest
try:
import urllib3
_urllib3_import_failed = False
except Except:
_urllib3_import_failed = True


@unittest.skipIf(_urllib3_import_failed or True, "urllib3 not available")
class TestUrllib3(unittest.TestCase):
""" #547 """

def setUp(self):
self.cfg = {'sync_request_timeout': 60*60}
self.server = ThreadedServer(SlaveService, port=18878, auto_register=False, protocol_config=self.cfg)
self.server.logger.quiet = False
self.server._start_in_thread()
self.conn = rpyc.classic.connect('localhost', port=18878)

def tearDown(self):
self.conn.close()
self.server.close()

def test_issue(self):
self.conn.execute('import urllib3')
urllib3_ = self.conn.modules.urllib3
headers = urllib3.HTTPHeaderDict()
headers.add("Accept", "application/json")
headers.add("Accept", "text/plain")
resp = urllib3_.request( "POST", "https://httpbin.org/post", fields={"hello": "world"}, headers={ "X-Request-Id": "test"})
__import__('code').interact(local=locals() | globals())


#self.assertTrue(self.conn.root.instance(remote_list, list))


if __name__ == "__main__":
unittest.main()

0 comments on commit f715399

Please sign in to comment.