• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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