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