blob: 8d2e776d72676d0226d9319c5efb40db9017d161 [file] [log] [blame]
Kevin Cheng757c2642019-04-18 11:31:16 -07001import 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:
Lucia Li6aa63b02021-11-08 21:45:14 +080066 raise OSError(("popen failed with exit code %r: %r\n\n%s" % (
67 err, args, output)).rstrip())
Kevin Cheng757c2642019-04-18 11:31:16 -070068 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:
Lucia Li6aa63b02021-11-08 21:45:14 +0800175 raise OSError("%r failed with exit code %r" % (
176 os.path.join(path, executable_name), err))
Kevin Cheng757c2642019-04-18 11:31:16 -0700177 return result
178
179
180class TestBasic(EmbeddingTests):
181 def test_empty(self):
182 empty_cffi = self.prepare_module('empty')
183
184 def test_basic(self):
185 add1_cffi = self.prepare_module('add1')
186 self.compile('add1-test', [add1_cffi])
187 output = self.execute('add1-test')
188 assert output == ("preparing...\n"
189 "adding 40 and 2\n"
190 "adding 100 and -5\n"
191 "got: 42 95\n")
192
193 def test_two_modules(self):
194 add1_cffi = self.prepare_module('add1')
195 add2_cffi = self.prepare_module('add2')
196 self.compile('add2-test', [add1_cffi, add2_cffi])
197 output = self.execute('add2-test')
198 assert output == ("preparing...\n"
199 "adding 40 and 2\n"
200 "prepADD2\n"
201 "adding 100 and -5 and -20\n"
202 "got: 42 75\n")
203
204 def test_init_time_error(self):
205 initerror_cffi = self.prepare_module('initerror')
206 self.compile('add1-test', [initerror_cffi])
207 output = self.execute('add1-test')
208 assert output == "got: 0 0\n" # plus lots of info to stderr
Lucia Li6aa63b02021-11-08 21:45:14 +0800209
210 def test_embedding_with_unicode(self):
211 withunicode_cffi = self.prepare_module('withunicode')
212 self.compile('add1-test', [withunicode_cffi])
213 output = self.execute('add1-test')
214 assert output == "255\n4660\n65244\ngot: 0 0\n"