1import difflib
2import glob
3import gzip
4import os
5import tempfile
6
7import Cython.Build.Dependencies
8import Cython.Utils
9from Cython.TestUtils import CythonTest
10
11
12class TestCyCache(CythonTest):
13
14    def setUp(self):
15        CythonTest.setUp(self)
16        self.temp_dir = tempfile.mkdtemp(
17            prefix='cycache-test',
18            dir='TEST_TMP' if os.path.isdir('TEST_TMP') else None)
19        self.src_dir = tempfile.mkdtemp(prefix='src', dir=self.temp_dir)
20        self.cache_dir = tempfile.mkdtemp(prefix='cache', dir=self.temp_dir)
21
22    def cache_files(self, file_glob):
23        return glob.glob(os.path.join(self.cache_dir, file_glob))
24
25    def fresh_cythonize(self, *args, **kwargs):
26        Cython.Utils.clear_function_caches()
27        Cython.Build.Dependencies._dep_tree = None  # discard method caches
28        Cython.Build.Dependencies.cythonize(*args, **kwargs)
29
30    def test_cycache_switch(self):
31        content1 = 'value = 1\n'
32        content2 = 'value = 2\n'
33        a_pyx = os.path.join(self.src_dir, 'a.pyx')
34        a_c = a_pyx[:-4] + '.c'
35
36        open(a_pyx, 'w').write(content1)
37        self.fresh_cythonize(a_pyx, cache=self.cache_dir)
38        self.fresh_cythonize(a_pyx, cache=self.cache_dir)
39        self.assertEqual(1, len(self.cache_files('a.c*')))
40        a_contents1 = open(a_c).read()
41        os.unlink(a_c)
42
43        open(a_pyx, 'w').write(content2)
44        self.fresh_cythonize(a_pyx, cache=self.cache_dir)
45        a_contents2 = open(a_c).read()
46        os.unlink(a_c)
47
48        self.assertNotEqual(a_contents1, a_contents2, 'C file not changed!')
49        self.assertEqual(2, len(self.cache_files('a.c*')))
50
51        open(a_pyx, 'w').write(content1)
52        self.fresh_cythonize(a_pyx, cache=self.cache_dir)
53        self.assertEqual(2, len(self.cache_files('a.c*')))
54        a_contents = open(a_c).read()
55        self.assertEqual(
56            a_contents, a_contents1,
57            msg='\n'.join(list(difflib.unified_diff(
58                a_contents.split('\n'), a_contents1.split('\n')))[:10]))
59
60    def test_cycache_uses_cache(self):
61        a_pyx = os.path.join(self.src_dir, 'a.pyx')
62        a_c = a_pyx[:-4] + '.c'
63        open(a_pyx, 'w').write('pass')
64        self.fresh_cythonize(a_pyx, cache=self.cache_dir)
65        a_cache = os.path.join(self.cache_dir, os.listdir(self.cache_dir)[0])
66        gzip.GzipFile(a_cache, 'wb').write('fake stuff'.encode('ascii'))
67        os.unlink(a_c)
68        self.fresh_cythonize(a_pyx, cache=self.cache_dir)
69        a_contents = open(a_c).read()
70        self.assertEqual(a_contents, 'fake stuff',
71                         'Unexpected contents: %s...' % a_contents[:100])
72
73    def test_multi_file_output(self):
74        a_pyx = os.path.join(self.src_dir, 'a.pyx')
75        a_c = a_pyx[:-4] + '.c'
76        a_h = a_pyx[:-4] + '.h'
77        a_api_h = a_pyx[:-4] + '_api.h'
78        open(a_pyx, 'w').write('cdef public api int foo(int x): return x\n')
79        self.fresh_cythonize(a_pyx, cache=self.cache_dir)
80        expected = [a_c, a_h, a_api_h]
81        for output in expected:
82            self.assertTrue(os.path.exists(output), output)
83            os.unlink(output)
84        self.fresh_cythonize(a_pyx, cache=self.cache_dir)
85        for output in expected:
86            self.assertTrue(os.path.exists(output), output)
87
88    def test_options_invalidation(self):
89        hash_pyx = os.path.join(self.src_dir, 'options.pyx')
90        hash_c = hash_pyx[:-len('.pyx')] + '.c'
91
92        open(hash_pyx, 'w').write('pass')
93        self.fresh_cythonize(hash_pyx, cache=self.cache_dir, cplus=False)
94        self.assertEqual(1, len(self.cache_files('options.c*')))
95
96        os.unlink(hash_c)
97        self.fresh_cythonize(hash_pyx, cache=self.cache_dir, cplus=True)
98        self.assertEqual(2, len(self.cache_files('options.c*')))
99
100        os.unlink(hash_c)
101        self.fresh_cythonize(hash_pyx, cache=self.cache_dir, cplus=False, show_version=False)
102        self.assertEqual(2, len(self.cache_files('options.c*')))
103
104        os.unlink(hash_c)
105        self.fresh_cythonize(hash_pyx, cache=self.cache_dir, cplus=False, show_version=True)
106        self.assertEqual(2, len(self.cache_files('options.c*')))
107