1import sys, os, imp, math, shutil 2import py 3from cffi import FFI, FFIError 4from cffi.verifier import Verifier, _locate_engine_class, _get_so_suffixes 5from cffi.ffiplatform import maybe_relative_path 6from testing.udir import udir 7 8 9class DistUtilsTest(object): 10 def setup_class(self): 11 self.lib_m = "m" 12 if sys.platform == 'win32': 13 #there is a small chance this fails on Mingw via environ $CC 14 import distutils.ccompiler 15 if distutils.ccompiler.get_default_compiler() == 'msvc': 16 self.lib_m = 'msvcrt' 17 18 def teardown_class(self): 19 if udir.isdir(): 20 udir.remove(ignore_errors=True) 21 udir.ensure(dir=1) 22 23 def test_locate_engine_class(self): 24 cls = _locate_engine_class(FFI(), self.generic) 25 if self.generic: 26 # asked for the generic engine, which must not generate a 27 # CPython extension module 28 assert not cls._gen_python_module 29 else: 30 # asked for the CPython engine: check that we got it, unless 31 # we are running on top of PyPy, where the generic engine is 32 # always better 33 if '__pypy__' not in sys.builtin_module_names: 34 assert cls._gen_python_module 35 36 def test_write_source(self): 37 ffi = FFI() 38 ffi.cdef("double sin(double x);") 39 csrc = '/*hi there %s!*/\n#include <math.h>\n' % self 40 v = Verifier(ffi, csrc, force_generic_engine=self.generic, 41 libraries=[self.lib_m]) 42 v.write_source() 43 with open(v.sourcefilename, 'r') as f: 44 data = f.read() 45 assert csrc in data 46 47 def test_write_source_explicit_filename(self): 48 ffi = FFI() 49 ffi.cdef("double sin(double x);") 50 csrc = '/*hi there %s!*/\n#include <math.h>\n' % self 51 v = Verifier(ffi, csrc, force_generic_engine=self.generic, 52 libraries=[self.lib_m]) 53 v.sourcefilename = filename = str(udir.join('write_source.c')) 54 v.write_source() 55 assert filename == v.sourcefilename 56 with open(filename, 'r') as f: 57 data = f.read() 58 assert csrc in data 59 60 def test_write_source_to_file_obj(self): 61 ffi = FFI() 62 ffi.cdef("double sin(double x);") 63 csrc = '/*hi there %s!*/\n#include <math.h>\n' % self 64 v = Verifier(ffi, csrc, force_generic_engine=self.generic, 65 libraries=[self.lib_m]) 66 try: 67 from StringIO import StringIO 68 except ImportError: 69 from io import StringIO 70 f = StringIO() 71 v.write_source(file=f) 72 assert csrc in f.getvalue() 73 74 def test_compile_module(self): 75 ffi = FFI() 76 ffi.cdef("double sin(double x);") 77 csrc = '/*hi there %s!*/\n#include <math.h>\n' % self 78 v = Verifier(ffi, csrc, force_generic_engine=self.generic, 79 libraries=[self.lib_m]) 80 v.compile_module() 81 assert v.get_module_name().startswith('_cffi_') 82 if v.generates_python_module(): 83 mod = imp.load_dynamic(v.get_module_name(), v.modulefilename) 84 assert hasattr(mod, '_cffi_setup') 85 86 def test_compile_module_explicit_filename(self): 87 ffi = FFI() 88 ffi.cdef("double sin(double x);") 89 csrc = '/*hi there %s!2*/\n#include <math.h>\n' % self 90 v = Verifier(ffi, csrc, force_generic_engine=self.generic, 91 libraries=[self.lib_m]) 92 basename = self.__class__.__name__[:10] + '_test_compile_module' 93 v.modulefilename = filename = str(udir.join(basename + '.so')) 94 v.compile_module() 95 assert filename == v.modulefilename 96 assert v.get_module_name() == basename 97 if v.generates_python_module(): 98 mod = imp.load_dynamic(v.get_module_name(), v.modulefilename) 99 assert hasattr(mod, '_cffi_setup') 100 101 def test_name_from_checksum_of_cdef(self): 102 names = [] 103 for csrc in ['double', 'double', 'float']: 104 ffi = FFI() 105 ffi.cdef("%s sin(double x);" % csrc) 106 v = Verifier(ffi, "#include <math.h>", 107 force_generic_engine=self.generic, 108 libraries=[self.lib_m]) 109 names.append(v.get_module_name()) 110 assert names[0] == names[1] != names[2] 111 112 def test_name_from_checksum_of_csrc(self): 113 names = [] 114 for csrc in ['123', '123', '1234']: 115 ffi = FFI() 116 ffi.cdef("double sin(double x);") 117 v = Verifier(ffi, csrc, force_generic_engine=self.generic) 118 names.append(v.get_module_name()) 119 assert names[0] == names[1] != names[2] 120 121 def test_load_library(self): 122 ffi = FFI() 123 ffi.cdef("double sin(double x);") 124 csrc = '/*hi there %s!3*/\n#include <math.h>\n' % self 125 v = Verifier(ffi, csrc, force_generic_engine=self.generic, 126 libraries=[self.lib_m]) 127 library = v.load_library() 128 assert library.sin(12.3) == math.sin(12.3) 129 130 def test_verifier_args(self): 131 ffi = FFI() 132 ffi.cdef("double sin(double x);") 133 csrc = '/*hi there %s!4*/#include "test_verifier_args.h"\n' % self 134 udir.join('test_verifier_args.h').write('#include <math.h>\n') 135 v = Verifier(ffi, csrc, include_dirs=[str(udir)], 136 force_generic_engine=self.generic, 137 libraries=[self.lib_m]) 138 library = v.load_library() 139 assert library.sin(12.3) == math.sin(12.3) 140 141 def test_verifier_object_from_ffi(self): 142 ffi = FFI() 143 ffi.cdef("double sin(double x);") 144 csrc = "/*6%s*/\n#include <math.h>" % self 145 lib = ffi.verify(csrc, force_generic_engine=self.generic, 146 libraries=[self.lib_m]) 147 assert lib.sin(12.3) == math.sin(12.3) 148 assert isinstance(ffi.verifier, Verifier) 149 with open(ffi.verifier.sourcefilename, 'r') as f: 150 data = f.read() 151 assert csrc in data 152 153 def test_extension_object(self): 154 ffi = FFI() 155 ffi.cdef("double sin(double x);") 156 csrc = '/*7%s*/' % self + ''' 157 #include <math.h> 158 #ifndef TEST_EXTENSION_OBJECT 159 # error "define_macros missing" 160 #endif 161 ''' 162 lib = ffi.verify(csrc, define_macros=[('TEST_EXTENSION_OBJECT', '1')], 163 force_generic_engine=self.generic, 164 libraries=[self.lib_m]) 165 assert lib.sin(12.3) == math.sin(12.3) 166 v = ffi.verifier 167 ext = v.get_extension() 168 assert 'distutils.extension.Extension' in str(ext.__class__) or \ 169 'setuptools.extension.Extension' in str(ext.__class__) 170 assert ext.sources == [maybe_relative_path(v.sourcefilename)] 171 assert ext.name == v.get_module_name() 172 assert ext.define_macros == [('TEST_EXTENSION_OBJECT', '1')] 173 174 def test_extension_forces_write_source(self): 175 ffi = FFI() 176 ffi.cdef("double sin(double x);") 177 csrc = '/*hi there9!%s*/\n#include <math.h>\n' % self 178 v = Verifier(ffi, csrc, force_generic_engine=self.generic, 179 libraries=[self.lib_m]) 180 assert not os.path.exists(v.sourcefilename) 181 v.get_extension() 182 assert os.path.exists(v.sourcefilename) 183 184 def test_extension_object_extra_sources(self): 185 ffi = FFI() 186 ffi.cdef("double test1eoes(double x);") 187 extra_source = str(udir.join('extension_extra_sources.c')) 188 with open(extra_source, 'w') as f: 189 f.write('double test1eoes(double x) { return x * 6.0; }\n') 190 csrc = '/*9%s*/' % self + ''' 191 double test1eoes(double x); /* or #include "extra_sources.h" */ 192 ''' 193 lib = ffi.verify(csrc, sources=[extra_source], 194 force_generic_engine=self.generic) 195 assert lib.test1eoes(7.0) == 42.0 196 v = ffi.verifier 197 ext = v.get_extension() 198 assert 'distutils.extension.Extension' in str(ext.__class__) or \ 199 'setuptools.extension.Extension' in str(ext.__class__) 200 assert ext.sources == [maybe_relative_path(v.sourcefilename), 201 extra_source] 202 assert ext.name == v.get_module_name() 203 204 def test_install_and_reload_module(self, targetpackage='', ext_package=''): 205 KEY = repr(self) 206 if not hasattr(os, 'fork'): 207 py.test.skip("test requires os.fork()") 208 209 if targetpackage: 210 udir.ensure(targetpackage, dir=1).ensure('__init__.py') 211 sys.path.insert(0, str(udir)) 212 213 def make_ffi(**verifier_args): 214 ffi = FFI() 215 ffi.cdef("/* %s, %s, %s */" % (KEY, targetpackage, ext_package)) 216 ffi.cdef("double test1iarm(double x);") 217 csrc = "double test1iarm(double x) { return x * 42.0; }" 218 lib = ffi.verify(csrc, force_generic_engine=self.generic, 219 ext_package=ext_package, 220 **verifier_args) 221 return ffi, lib 222 223 childpid = os.fork() 224 if childpid == 0: 225 # in the child 226 ffi, lib = make_ffi() 227 assert lib.test1iarm(1.5) == 63.0 228 # "install" the module by moving it into udir (/targetpackage) 229 if targetpackage: 230 target = udir.join(targetpackage) 231 else: 232 target = udir 233 shutil.move(ffi.verifier.modulefilename, str(target)) 234 os._exit(0) 235 # in the parent 236 _, status = os.waitpid(childpid, 0) 237 if not (os.WIFEXITED(status) and os.WEXITSTATUS(status) == 0): 238 raise AssertionError # see error above in subprocess 239 240 from cffi import ffiplatform 241 prev_compile = ffiplatform.compile 242 try: 243 if targetpackage == ext_package: 244 ffiplatform.compile = lambda *args: dont_call_me_any_more 245 # won't find it in tmpdir, but should find it correctly 246 # installed in udir 247 ffi, lib = make_ffi() 248 assert lib.test1iarm(0.5) == 21.0 249 finally: 250 ffiplatform.compile = prev_compile 251 252 def test_install_and_reload_module_package(self): 253 self.test_install_and_reload_module(targetpackage='foo_iarmp', 254 ext_package='foo_iarmp') 255 256 def test_install_and_reload_module_ext_package_not_found(self): 257 self.test_install_and_reload_module(targetpackage='foo_epnf', 258 ext_package='not_found') 259 260 def test_tag(self): 261 ffi = FFI() 262 ffi.cdef("/* %s test_tag */ double test1tag(double x);" % self) 263 csrc = "double test1tag(double x) { return x - 42.0; }" 264 lib = ffi.verify(csrc, force_generic_engine=self.generic, 265 tag='xxtest_tagxx') 266 assert lib.test1tag(143) == 101.0 267 assert '_cffi_xxtest_tagxx_' in ffi.verifier.modulefilename 268 269 def test_modulename(self): 270 ffi = FFI() 271 ffi.cdef("/* %s test_modulename */ double test1foo(double x);" % self) 272 csrc = "double test1foo(double x) { return x - 63.0; }" 273 modname = 'xxtest_modulenamexx%d' % (self.generic,) 274 lib = ffi.verify(csrc, force_generic_engine=self.generic, 275 modulename=modname) 276 assert lib.test1foo(143) == 80.0 277 suffix = _get_so_suffixes()[0] 278 fn1 = os.path.join(ffi.verifier.tmpdir, modname + '.c') 279 fn2 = os.path.join(ffi.verifier.tmpdir, modname + suffix) 280 assert ffi.verifier.sourcefilename == fn1 281 assert ffi.verifier.modulefilename == fn2 282 283 284class TestDistUtilsCPython(DistUtilsTest): 285 generic = False 286 287class TestDistUtilsGeneric(DistUtilsTest): 288 generic = True 289