1import sys, os, py 2import subprocess 3import cffi 4from testing.udir import udir 5from shutil import rmtree 6from tempfile import mkdtemp 7 8 9def chdir_to_tmp(f): 10 f.chdir_to_tmp = True 11 return f 12 13def from_outside(f): 14 f.chdir_to_tmp = False 15 return f 16 17 18class TestDist(object): 19 20 def setup_method(self, meth): 21 self.executable = os.path.abspath(sys.executable) 22 self.rootdir = os.path.abspath(os.path.dirname(os.path.dirname( 23 cffi.__file__))) 24 self.udir = udir.join(meth.__name__) 25 os.mkdir(str(self.udir)) 26 if meth.chdir_to_tmp: 27 self.saved_cwd = os.getcwd() 28 os.chdir(str(self.udir)) 29 30 def teardown_method(self, meth): 31 if hasattr(self, 'saved_cwd'): 32 os.chdir(self.saved_cwd) 33 34 def run(self, args, cwd=None): 35 env = os.environ.copy() 36 # a horrible hack to prevent distutils from finding ~/.pydistutils.cfg 37 # (there is the --no-user-cfg option, but not in Python 2.6...) 38 # NOTE: pointing $HOME to a nonexistent directory can break certain things 39 # that look there for configuration (like ccache). 40 tmp_home = mkdtemp() 41 assert tmp_home != None, "cannot create temporary homedir" 42 env['HOME'] = tmp_home 43 if cwd is None: 44 newpath = self.rootdir 45 if 'PYTHONPATH' in env: 46 newpath += os.pathsep + env['PYTHONPATH'] 47 env['PYTHONPATH'] = newpath 48 try: 49 subprocess.check_call([self.executable] + args, cwd=cwd, env=env) 50 finally: 51 rmtree(tmp_home) 52 53 def _prepare_setuptools(self): 54 if hasattr(TestDist, '_setuptools_ready'): 55 return 56 try: 57 import setuptools 58 except ImportError: 59 py.test.skip("setuptools not found") 60 if os.path.exists(os.path.join(self.rootdir, 'setup.py')): 61 self.run(['setup.py', 'egg_info'], cwd=self.rootdir) 62 TestDist._setuptools_ready = True 63 64 def check_produced_files(self, content, curdir=None): 65 if curdir is None: 66 curdir = str(self.udir) 67 found_so = None 68 for name in os.listdir(curdir): 69 if (name.endswith('.so') or name.endswith('.pyd') or 70 name.endswith('.dylib') or name.endswith('.dll')): 71 found_so = os.path.join(curdir, name) 72 # foo.so => foo 73 parts = name.split('.') 74 del parts[-1] 75 if len(parts) > 1 and parts[-1] != 'bar': 76 # foo.cpython-34m.so => foo, but foo.bar.so => foo.bar 77 del parts[-1] 78 name = '.'.join(parts) 79 # foo_d => foo (Python 2 debug builds) 80 if name.endswith('_d') and hasattr(sys, 'gettotalrefcount'): 81 name = name[:-2] 82 name += '.SO' 83 if name.startswith('pycparser') and name.endswith('.egg'): 84 continue # no clue why this shows up sometimes and not others 85 if name == '.eggs': 86 continue # seems new in 3.5, ignore it 87 assert name in content, "found unexpected file %r" % ( 88 os.path.join(curdir, name),) 89 value = content.pop(name) 90 if value is None: 91 assert name.endswith('.SO') or ( 92 os.path.isfile(os.path.join(curdir, name))) 93 else: 94 subdir = os.path.join(curdir, name) 95 assert os.path.isdir(subdir) 96 if value == '?': 97 continue 98 found_so = self.check_produced_files(value, subdir) or found_so 99 assert content == {}, "files or dirs not produced in %r: %r" % ( 100 curdir, content.keys()) 101 return found_so 102 103 @chdir_to_tmp 104 def test_empty(self): 105 self.check_produced_files({}) 106 107 @chdir_to_tmp 108 def test_abi_emit_python_code_1(self): 109 ffi = cffi.FFI() 110 ffi.set_source("package_name_1.mymod", None) 111 ffi.emit_python_code('xyz.py') 112 self.check_produced_files({'xyz.py': None}) 113 114 @chdir_to_tmp 115 def test_abi_emit_python_code_2(self): 116 ffi = cffi.FFI() 117 ffi.set_source("package_name_1.mymod", None) 118 py.test.raises(IOError, ffi.emit_python_code, 'unexisting/xyz.py') 119 120 @from_outside 121 def test_abi_emit_python_code_3(self): 122 ffi = cffi.FFI() 123 ffi.set_source("package_name_1.mymod", None) 124 ffi.emit_python_code(str(self.udir.join('xyt.py'))) 125 self.check_produced_files({'xyt.py': None}) 126 127 @chdir_to_tmp 128 def test_abi_compile_1(self): 129 ffi = cffi.FFI() 130 ffi.set_source("mod_name_in_package.mymod", None) 131 x = ffi.compile() 132 self.check_produced_files({'mod_name_in_package': {'mymod.py': None}}) 133 assert x == os.path.join('.', 'mod_name_in_package', 'mymod.py') 134 135 @chdir_to_tmp 136 def test_abi_compile_2(self): 137 ffi = cffi.FFI() 138 ffi.set_source("mod_name_in_package.mymod", None) 139 x = ffi.compile('build2') 140 self.check_produced_files({'build2': { 141 'mod_name_in_package': {'mymod.py': None}}}) 142 assert x == os.path.join('build2', 'mod_name_in_package', 'mymod.py') 143 144 @from_outside 145 def test_abi_compile_3(self): 146 ffi = cffi.FFI() 147 ffi.set_source("mod_name_in_package.mymod", None) 148 tmpdir = str(self.udir.join('build3')) 149 x = ffi.compile(tmpdir) 150 self.check_produced_files({'build3': { 151 'mod_name_in_package': {'mymod.py': None}}}) 152 assert x == os.path.join(tmpdir, 'mod_name_in_package', 'mymod.py') 153 154 @chdir_to_tmp 155 def test_api_emit_c_code_1(self): 156 ffi = cffi.FFI() 157 ffi.set_source("package_name_1.mymod", "/*code would be here*/") 158 ffi.emit_c_code('xyz.c') 159 self.check_produced_files({'xyz.c': None}) 160 161 @chdir_to_tmp 162 def test_api_emit_c_code_2(self): 163 ffi = cffi.FFI() 164 ffi.set_source("package_name_1.mymod", "/*code would be here*/") 165 py.test.raises(IOError, ffi.emit_c_code, 'unexisting/xyz.c') 166 167 @from_outside 168 def test_api_emit_c_code_3(self): 169 ffi = cffi.FFI() 170 ffi.set_source("package_name_1.mymod", "/*code would be here*/") 171 ffi.emit_c_code(str(self.udir.join('xyu.c'))) 172 self.check_produced_files({'xyu.c': None}) 173 174 @chdir_to_tmp 175 def test_api_compile_1(self): 176 ffi = cffi.FFI() 177 ffi.set_source("mod_name_in_package.mymod", "/*code would be here*/") 178 x = ffi.compile() 179 if sys.platform != 'win32': 180 sofile = self.check_produced_files({ 181 'mod_name_in_package': {'mymod.SO': None, 182 'mymod.c': None, 183 'mymod.o': None}}) 184 assert os.path.isabs(x) and os.path.samefile(x, sofile) 185 else: 186 self.check_produced_files({ 187 'mod_name_in_package': {'mymod.SO': None, 188 'mymod.c': None}, 189 'Release': '?'}) 190 191 @chdir_to_tmp 192 def test_api_compile_2(self): 193 ffi = cffi.FFI() 194 ffi.set_source("mod_name_in_package.mymod", "/*code would be here*/") 195 x = ffi.compile('output') 196 if sys.platform != 'win32': 197 sofile = self.check_produced_files({ 198 'output': {'mod_name_in_package': {'mymod.SO': None, 199 'mymod.c': None, 200 'mymod.o': None}}}) 201 assert os.path.isabs(x) and os.path.samefile(x, sofile) 202 else: 203 self.check_produced_files({ 204 'output': {'mod_name_in_package': {'mymod.SO': None, 205 'mymod.c': None}, 206 'Release': '?'}}) 207 208 @from_outside 209 def test_api_compile_3(self): 210 ffi = cffi.FFI() 211 ffi.set_source("mod_name_in_package.mymod", "/*code would be here*/") 212 x = ffi.compile(str(self.udir.join('foo'))) 213 if sys.platform != 'win32': 214 sofile = self.check_produced_files({ 215 'foo': {'mod_name_in_package': {'mymod.SO': None, 216 'mymod.c': None, 217 'mymod.o': None}}}) 218 assert os.path.isabs(x) and os.path.samefile(x, sofile) 219 else: 220 self.check_produced_files({ 221 'foo': {'mod_name_in_package': {'mymod.SO': None, 222 'mymod.c': None}, 223 'Release': '?'}}) 224 225 @chdir_to_tmp 226 def test_api_compile_explicit_target_1(self): 227 ffi = cffi.FFI() 228 ffi.set_source("mod_name_in_package.mymod", "/*code would be here*/") 229 x = ffi.compile(target="foo.bar.*") 230 if sys.platform != 'win32': 231 sofile = self.check_produced_files({ 232 'mod_name_in_package': {'foo.bar.SO': None, 233 'mymod.c': None, 234 'mymod.o': None}}) 235 assert os.path.isabs(x) and os.path.samefile(x, sofile) 236 else: 237 self.check_produced_files({ 238 'mod_name_in_package': {'foo.bar.SO': None, 239 'mymod.c': None}, 240 'Release': '?'}) 241 242 @chdir_to_tmp 243 def test_api_compile_explicit_target_3(self): 244 ffi = cffi.FFI() 245 ffi.set_source("mod_name_in_package.mymod", "/*code would be here*/") 246 x = ffi.compile(target="foo.bar.baz") 247 if sys.platform != 'win32': 248 self.check_produced_files({ 249 'mod_name_in_package': {'foo.bar.baz': None, 250 'mymod.c': None, 251 'mymod.o': None}}) 252 sofile = os.path.join(str(self.udir), 253 'mod_name_in_package', 'foo.bar.baz') 254 assert os.path.isabs(x) and os.path.samefile(x, sofile) 255 else: 256 self.check_produced_files({ 257 'mod_name_in_package': {'foo.bar.baz': None, 258 'mymod.c': None}, 259 'Release': '?'}) 260 261 @chdir_to_tmp 262 def test_api_distutils_extension_1(self): 263 ffi = cffi.FFI() 264 ffi.set_source("mod_name_in_package.mymod", "/*code would be here*/") 265 ext = ffi.distutils_extension() 266 self.check_produced_files({'build': { 267 'mod_name_in_package': {'mymod.c': None}}}) 268 if hasattr(os.path, 'samefile'): 269 assert os.path.samefile(ext.sources[0], 270 'build/mod_name_in_package/mymod.c') 271 272 @from_outside 273 def test_api_distutils_extension_2(self): 274 ffi = cffi.FFI() 275 ffi.set_source("mod_name_in_package.mymod", "/*code would be here*/") 276 ext = ffi.distutils_extension(str(self.udir.join('foo'))) 277 self.check_produced_files({'foo': { 278 'mod_name_in_package': {'mymod.c': None}}}) 279 if hasattr(os.path, 'samefile'): 280 assert os.path.samefile(ext.sources[0], 281 str(self.udir.join('foo/mod_name_in_package/mymod.c'))) 282 283 284 def _make_distutils_api(self): 285 os.mkdir("src") 286 os.mkdir(os.path.join("src", "pack1")) 287 with open(os.path.join("src", "pack1", "__init__.py"), "w") as f: 288 pass 289 with open("setup.py", "w") as f: 290 f.write("""if 1: 291 # https://bugs.python.org/issue23246 292 import sys 293 if sys.platform == 'win32': 294 try: 295 import setuptools 296 except ImportError: 297 pass 298 299 import cffi 300 ffi = cffi.FFI() 301 ffi.set_source("pack1.mymod", "/*code would be here*/") 302 303 from distutils.core import setup 304 setup(name='example1', 305 version='0.1', 306 packages=['pack1'], 307 package_dir={'': 'src'}, 308 ext_modules=[ffi.distutils_extension()]) 309 """) 310 311 @chdir_to_tmp 312 def test_distutils_api_1(self): 313 self._make_distutils_api() 314 self.run(["setup.py", "build"]) 315 self.check_produced_files({'setup.py': None, 316 'build': '?', 317 'src': {'pack1': {'__init__.py': None}}}) 318 319 @chdir_to_tmp 320 def test_distutils_api_2(self): 321 self._make_distutils_api() 322 self.run(["setup.py", "build_ext", "-i"]) 323 self.check_produced_files({'setup.py': None, 324 'build': '?', 325 'src': {'pack1': {'__init__.py': None, 326 'mymod.SO': None}}}) 327 328 def _make_setuptools_abi(self): 329 self._prepare_setuptools() 330 os.mkdir("src0") 331 os.mkdir(os.path.join("src0", "pack2")) 332 with open(os.path.join("src0", "pack2", "__init__.py"), "w") as f: 333 pass 334 with open(os.path.join("src0", "pack2", "_build.py"), "w") as f: 335 f.write("""if 1: 336 import cffi 337 ffi = cffi.FFI() 338 ffi.set_source("pack2.mymod", None) 339 """) 340 with open("setup.py", "w") as f: 341 f.write("""if 1: 342 from setuptools import setup 343 setup(name='example1', 344 version='0.1', 345 packages=['pack2'], 346 package_dir={'': 'src0'}, 347 cffi_modules=["src0/pack2/_build.py:ffi"]) 348 """) 349 350 @chdir_to_tmp 351 def test_setuptools_abi_1(self): 352 self._make_setuptools_abi() 353 self.run(["setup.py", "build"]) 354 self.check_produced_files({'setup.py': None, 355 'build': '?', 356 'src0': {'pack2': {'__init__.py': None, 357 '_build.py': None}}}) 358 359 @chdir_to_tmp 360 def test_setuptools_abi_2(self): 361 self._make_setuptools_abi() 362 self.run(["setup.py", "build_ext", "-i"]) 363 self.check_produced_files({'setup.py': None, 364 'src0': {'pack2': {'__init__.py': None, 365 '_build.py': None, 366 'mymod.py': None}}}) 367 368 def _make_setuptools_api(self): 369 self._prepare_setuptools() 370 os.mkdir("src1") 371 os.mkdir(os.path.join("src1", "pack3")) 372 with open(os.path.join("src1", "pack3", "__init__.py"), "w") as f: 373 pass 374 with open(os.path.join("src1", "pack3", "_build.py"), "w") as f: 375 f.write("""if 1: 376 import cffi 377 ffi = cffi.FFI() 378 ffi.set_source("pack3.mymod", "/*code would be here*/") 379 ffi._hi_there = 42 380 """) 381 with open("setup.py", "w") as f: 382 f.write("from __future__ import print_function\n" 383 """if 1: 384 from setuptools import setup 385 from distutils.command.build_ext import build_ext 386 import os 387 388 class TestBuildExt(build_ext): 389 def pre_run(self, ext, ffi): 390 print('_make_setuptools_api: in pre_run:', end=" ") 391 assert ffi._hi_there == 42 392 assert ext.name == "pack3.mymod" 393 fn = os.path.join(os.path.dirname(self.build_lib), 394 '..', 'see_me') 395 print('creating %r' % (fn,)) 396 open(fn, 'w').close() 397 398 setup(name='example1', 399 version='0.1', 400 packages=['pack3'], 401 package_dir={'': 'src1'}, 402 cffi_modules=["src1/pack3/_build.py:ffi"], 403 cmdclass={'build_ext': TestBuildExt}, 404 ) 405 """) 406 407 @chdir_to_tmp 408 def test_setuptools_api_1(self): 409 self._make_setuptools_api() 410 self.run(["setup.py", "build"]) 411 self.check_produced_files({'setup.py': None, 412 'build': '?', 413 'see_me': None, 414 'src1': {'pack3': {'__init__.py': None, 415 '_build.py': None}}}) 416 417 @chdir_to_tmp 418 def test_setuptools_api_2(self): 419 self._make_setuptools_api() 420 self.run(["setup.py", "build_ext", "-i"]) 421 self.check_produced_files({'setup.py': None, 422 'build': '?', 423 'see_me': None, 424 'src1': {'pack3': {'__init__.py': None, 425 '_build.py': None, 426 'mymod.SO': None}}}) 427