1import py 2import sys, os, re 3import shutil, subprocess, time 4from testing.udir import udir 5import cffi 6 7 8local_dir = os.path.dirname(os.path.abspath(__file__)) 9_link_error = '?' 10 11def check_lib_python_found(tmpdir): 12 global _link_error 13 if _link_error == '?': 14 ffi = cffi.FFI() 15 kwds = {} 16 ffi._apply_embedding_fix(kwds) 17 ffi.set_source("_test_lib_python_found", "", **kwds) 18 try: 19 ffi.compile(tmpdir=tmpdir, verbose=True) 20 except cffi.VerificationError as e: 21 _link_error = e 22 else: 23 _link_error = None 24 if _link_error: 25 py.test.skip(str(_link_error)) 26 27 28def prefix_pythonpath(): 29 cffi_base = os.path.dirname(os.path.dirname(local_dir)) 30 pythonpath = org_env.get('PYTHONPATH', '').split(os.pathsep) 31 if cffi_base not in pythonpath: 32 pythonpath.insert(0, cffi_base) 33 return os.pathsep.join(pythonpath) 34 35def copy_away_env(): 36 global org_env 37 try: 38 org_env 39 except NameError: 40 org_env = os.environ.copy() 41 42 43class EmbeddingTests: 44 _compiled_modules = {} 45 46 def setup_method(self, meth): 47 check_lib_python_found(str(udir.ensure('embedding', dir=1))) 48 self._path = udir.join('embedding', meth.__name__) 49 if sys.platform == "win32" or sys.platform == "darwin": 50 self._compiled_modules.clear() # workaround 51 52 def get_path(self): 53 return str(self._path.ensure(dir=1)) 54 55 def _run_base(self, args, **kwds): 56 print('RUNNING:', args, kwds) 57 return subprocess.Popen(args, **kwds) 58 59 def _run(self, args): 60 popen = self._run_base(args, cwd=self.get_path(), 61 stdout=subprocess.PIPE, 62 universal_newlines=True) 63 output = popen.stdout.read() 64 err = popen.wait() 65 if err: 66 raise OSError("popen failed with exit code %r: %r" % ( 67 err, args)) 68 print(output.rstrip()) 69 return output 70 71 def prepare_module(self, name): 72 self.patch_environment() 73 if name not in self._compiled_modules: 74 path = self.get_path() 75 filename = '%s.py' % name 76 # NOTE: if you have an .egg globally installed with an older 77 # version of cffi, this will not work, because sys.path ends 78 # up with the .egg before the PYTHONPATH entries. I didn't 79 # find a solution to that: we could hack sys.path inside the 80 # script run here, but we can't hack it in the same way in 81 # execute(). 82 pathname = os.path.join(path, filename) 83 with open(pathname, 'w') as g: 84 g.write(''' 85# https://bugs.python.org/issue23246 86import sys 87if sys.platform == 'win32': 88 try: 89 import setuptools 90 except ImportError: 91 pass 92''') 93 with open(os.path.join(local_dir, filename), 'r') as f: 94 g.write(f.read()) 95 96 output = self._run([sys.executable, pathname]) 97 match = re.compile(r"\bFILENAME: (.+)").search(output) 98 assert match 99 dynamic_lib_name = match.group(1) 100 if sys.platform == 'win32': 101 assert dynamic_lib_name.endswith('_cffi.dll') 102 elif sys.platform == 'darwin': 103 assert dynamic_lib_name.endswith('_cffi.dylib') 104 else: 105 assert dynamic_lib_name.endswith('_cffi.so') 106 self._compiled_modules[name] = dynamic_lib_name 107 return self._compiled_modules[name] 108 109 def compile(self, name, modules, opt=False, threads=False, defines={}): 110 path = self.get_path() 111 filename = '%s.c' % name 112 shutil.copy(os.path.join(local_dir, filename), path) 113 shutil.copy(os.path.join(local_dir, 'thread-test.h'), path) 114 import distutils.ccompiler 115 curdir = os.getcwd() 116 try: 117 os.chdir(self.get_path()) 118 c = distutils.ccompiler.new_compiler() 119 print('compiling %s with %r' % (name, modules)) 120 extra_preargs = [] 121 debug = True 122 if sys.platform == 'win32': 123 libfiles = [] 124 for m in modules: 125 m = os.path.basename(m) 126 assert m.endswith('.dll') 127 libfiles.append('Release\\%s.lib' % m[:-4]) 128 modules = libfiles 129 extra_preargs.append('/MANIFEST') 130 debug = False # you need to install extra stuff 131 # for this to work 132 elif threads: 133 extra_preargs.append('-pthread') 134 objects = c.compile([filename], macros=sorted(defines.items()), 135 debug=debug) 136 c.link_executable(objects + modules, name, extra_preargs=extra_preargs) 137 finally: 138 os.chdir(curdir) 139 140 def patch_environment(self): 141 copy_away_env() 142 path = self.get_path() 143 # for libpypy-c.dll or Python27.dll 144 path = os.path.split(sys.executable)[0] + os.path.pathsep + path 145 env_extra = {'PYTHONPATH': prefix_pythonpath()} 146 if sys.platform == 'win32': 147 envname = 'PATH' 148 else: 149 envname = 'LD_LIBRARY_PATH' 150 libpath = org_env.get(envname) 151 if libpath: 152 libpath = path + os.path.pathsep + libpath 153 else: 154 libpath = path 155 env_extra[envname] = libpath 156 for key, value in sorted(env_extra.items()): 157 if os.environ.get(key) != value: 158 print('* setting env var %r to %r' % (key, value)) 159 os.environ[key] = value 160 161 def execute(self, name): 162 path = self.get_path() 163 print('running %r in %r' % (name, path)) 164 executable_name = name 165 if sys.platform == 'win32': 166 executable_name = os.path.join(path, executable_name + '.exe') 167 else: 168 executable_name = os.path.join('.', executable_name) 169 popen = self._run_base([executable_name], cwd=path, 170 stdout=subprocess.PIPE, 171 universal_newlines=True) 172 result = popen.stdout.read() 173 err = popen.wait() 174 if err: 175 raise OSError("%r failed with exit code %r" % (name, err)) 176 return result 177 178 179class TestBasic(EmbeddingTests): 180 def test_empty(self): 181 empty_cffi = self.prepare_module('empty') 182 183 def test_basic(self): 184 add1_cffi = self.prepare_module('add1') 185 self.compile('add1-test', [add1_cffi]) 186 output = self.execute('add1-test') 187 assert output == ("preparing...\n" 188 "adding 40 and 2\n" 189 "adding 100 and -5\n" 190 "got: 42 95\n") 191 192 def test_two_modules(self): 193 add1_cffi = self.prepare_module('add1') 194 add2_cffi = self.prepare_module('add2') 195 self.compile('add2-test', [add1_cffi, add2_cffi]) 196 output = self.execute('add2-test') 197 assert output == ("preparing...\n" 198 "adding 40 and 2\n" 199 "prepADD2\n" 200 "adding 100 and -5 and -20\n" 201 "got: 42 75\n") 202 203 def test_init_time_error(self): 204 initerror_cffi = self.prepare_module('initerror') 205 self.compile('add1-test', [initerror_cffi]) 206 output = self.execute('add1-test') 207 assert output == "got: 0 0\n" # plus lots of info to stderr 208