import sys import py from pytest import mark, raises from support import ( IS_CLANG_REPL, IS_LINUX_ARM, IS_MAC, IS_VALGRIND, setup_make, ) currpath = py.path.local(__file__).dirpath() test_dct = str(currpath.join("cpp/pythonizablesDict")) def setup_module(mod): setup_make("pythonizables") class TestClassPYTHONIZATION: def setup_class(cls): cls.test_dct = test_dct import cppjit cls.pyzables = cppjit.load_reflection_info(cls.test_dct) @mark.xfail(condition=IS_MAC, reason="Fails on OSX") def test00_api(self): """Test basic semantics of the pythonization API""" import cppjit raises(TypeError, cppjit.py.add_pythonization, 1) def pythonizor1(klass, name): pass def pythonizor2(klass, name): pass pythonizor3 = pythonizor1 cppjit.py.add_pythonization(pythonizor1) assert cppjit.py.remove_pythonization(pythonizor2) == False assert cppjit.py.remove_pythonization(pythonizor3) == True def pythonizor(klass, name): if name == "pyzables::SomeDummy1": klass.test = 1 cppjit.py.add_pythonization(pythonizor) assert cppjit.gbl.pyzables.SomeDummy1.test == 1 def pythonizor(klass, name): if name == "SomeDummy2": klass.test = 2 cppjit.py.add_pythonization(pythonizor, "pyzables") # global pythonizors are still run even if namespaced ones available def pythonizor(klass, name): if name == "pyzables::SomeDummy2": klass.test = 3 cppjit.py.add_pythonization(pythonizor) assert cppjit.gbl.pyzables.SomeDummy2.test == 3 cppjit.cppdef(""" namespace pyzables { class TObjString { public: std::string s; TObjString(std::string ss) : s(ss) {} size_t Sizeof() { return s.size() + 1; } }; } """) def root_pythonizor(klass, name): if name == "pyzables::TObjString": klass.__len__ = klass.Sizeof cppjit.py.add_pythonization(root_pythonizor) assert len(cppjit.gbl.pyzables.TObjString("aap")) == 4 # include '\0' def test01_size_mapping(self): """Use composites to map GetSize() onto buffer returns""" import cppjit def set_size(self, buf): buf.reshape((self.GetN(),)) return buf cppjit.py.add_pythonization( cppjit.py.compose_method("NakedBuffers$", "Get[XY]$", set_size), "pyzables" ) bsize, xval, yval = 3, 2, 5 m = cppjit.gbl.pyzables.NakedBuffers(bsize, xval, yval) x = m.GetX() assert len(x) == bsize assert list(x) == list(map(lambda x: x * xval, range(bsize))) y = m.GetY() assert len(y) == bsize assert list(y) == list(map(lambda x: x * yval, range(bsize))) def test02_size_mapping_of_templated_method(self): """Use composites to map GetSize() onto buffer returns""" import cppjit def set_size(self, buf): buf.reshape((self.GetN(),)) return buf cppjit.py.add_pythonization( cppjit.py.compose_method("NakedBuffers2.*Vector.*", "Get[XY]$", set_size), "pyzables", ) bsize, xval, yval = 3, 2, 5 m = cppjit.gbl.pyzables.NakedBuffers2[cppjit.gbl.pyzables.Vector]( bsize, xval, yval ) x = m.GetX() assert len(x) == bsize assert list(x) == list(map(lambda x: x * xval, range(bsize))) y = m.GetY() assert len(y) == bsize assert list(y) == list(map(lambda x: x * yval, range(bsize))) def test03_type_pinning(self): """Verify pinnability of returns""" import cppjit cppjit.gbl.pyzables.GimeDerived.__creates__ = True result = cppjit.gbl.pyzables.GimeDerived() assert type(result) == cppjit.gbl.pyzables.MyDerived cppjit.py.pin_type(cppjit.gbl.pyzables.MyBase) assert type(result) == cppjit.gbl.pyzables.MyDerived def test04_transparency(self): """Transparent use of smart pointers""" import cppjit Countable = cppjit.gbl.pyzables.Countable mine = cppjit.gbl.pyzables.mine assert type(mine) == Countable assert mine.m_check == 0xCDCDCDCD assert type(mine.__smartptr__()) == cppjit.gbl.std.shared_ptr(Countable) assert mine.__smartptr__().get().m_check == 0xCDCDCDCD assert mine.say_hi() == "Hi!" @mark.xfail( run=False, condition=IS_VALGRIND and IS_LINUX_ARM and IS_CLANG_REPL, reason="Crashes on Valgind Clang-Repl-ARM", ) def test05_converters(self): """Smart pointer argument passing""" import cppjit pz = cppjit.gbl.pyzables mine = pz.mine assert 0xCDCDCDCD == pz.pass_mine_rp_ptr(mine) assert 0xCDCDCDCD == pz.pass_mine_rp_ref(mine) assert 0xCDCDCDCD == pz.pass_mine_rp(mine) assert 0xCDCDCDCD == pz.pass_mine_sp_ptr(mine) assert 0xCDCDCDCD == pz.pass_mine_sp_ref(mine) assert 0xCDCDCDCD == pz.pass_mine_sp_ptr(mine.__smartptr__()) assert 0xCDCDCDCD == pz.pass_mine_sp_ref(mine.__smartptr__()) assert 0xCDCDCDCD == pz.pass_mine_sp(mine) assert 0xCDCDCDCD == pz.pass_mine_sp(mine.__smartptr__()) # TODO: # cppjit.gbl.mine = mine pz.renew_mine() @mark.xfail( run=False, condition=IS_VALGRIND and IS_LINUX_ARM and IS_CLANG_REPL, reason="Fails with Valgrind with Clang-Repl ARM", ) def test06_executors(self): """Smart pointer return types""" import cppjit pz = cppjit.gbl.pyzables Countable = pz.Countable mine = pz.gime_mine_ptr() assert type(mine) == Countable assert mine.m_check == 0xCDCDCDCD assert type(mine.__smartptr__()) == cppjit.gbl.std.shared_ptr(Countable) assert mine.__smartptr__().get().m_check == 0xCDCDCDCD assert mine.say_hi() == "Hi!" mine = pz.gime_mine_ref() assert type(mine) == Countable assert mine.m_check == 0xCDCDCDCD assert type(mine.__smartptr__()) == cppjit.gbl.std.shared_ptr(Countable) assert mine.__smartptr__().get().m_check == 0xCDCDCDCD assert mine.say_hi() == "Hi!" mine = pz.gime_mine() assert type(mine) == Countable assert mine.m_check == 0xCDCDCDCD assert type(mine.__smartptr__()) == cppjit.gbl.std.shared_ptr(Countable) assert mine.__smartptr__().get().m_check == 0xCDCDCDCD assert mine.say_hi() == "Hi!" def test07_creates_flag(self): """Effect of creates flag on return type""" import gc import cppjit pz = cppjit.gbl.pyzables Countable = pz.Countable gc.collect() oldcount = Countable.sInstances # there's eg. one global variable pz.gime_naked_countable.__creates__ = True for i in range(10): cnt = pz.gime_naked_countable() gc.collect() assert Countable.sInstances == oldcount + 1 del cnt gc.collect() assert Countable.sInstances == oldcount def test08_base_class_pythonization(self): """Derived class should not re-pythonize base class pythonization""" import cppjit d = cppjit.gbl.pyzables.IndexableDerived() assert d[0] == 42 assert d[-1] == 42 # skip the IndexErorr test: pythonization for __getitem__[index] < size() # can not be applied strict enough (instead of an index, this could be an # associative container, with 'index' a key, not a counter # raises(IndexError, d.__getitem__, 1) @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test09_cpp_side_pythonization(self): """Use of C++ side pythonizations""" import cppjit # explicit pythonization for kls in [ cppjit.gbl.pyzables.WithCallback1, cppjit.gbl.pyzables.WithCallback2, ]: w = kls(42) assert hasattr(w, "GetInt") assert not hasattr(w, "get_int") assert w.GetInt() == 42 assert hasattr(w, "SetInt") assert not hasattr(w, "set_int") w.SetInt(17) assert w.GetInt() == 17 assert kls.klass_name == kls.__cpp_name__ # up-the-hierarchy pythonization w = cppjit.gbl.pyzables.WithCallback3(42) assert hasattr(w, "GetInt") assert not hasattr(w, "get_int") assert w.GetInt() == 2 * 42 assert hasattr(w, "SetInt") assert not hasattr(w, "set_int") w.SetInt(17) assert w.GetInt() == 4 * 17 assert cppjit.gbl.pyzables.WithCallback2.klass_name == "pyzables::WithCallback3" @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test10_shared_ptr_reset(self): """Checks that smart pointer types are Pythonized with the special __smartptr__ member that can also be used to reset the underlying smart pointer.""" import cppjit optr = cppjit.gbl.std.make_shared["std::string"]("hello smart pointer") o2 = cppjit.gbl.std.string() cppjit._backend.SetOwnership( o2, False ) # This object will be owned by the smart pointer optr.__smartptr__().reset(o2) assert optr == o2 ## actual test run if __name__ == "__main__": result = run_pytest(__file__) # noqa: F821 sys.exit(result)