1# -*- coding: utf-8 -*-
2# tag: ipython
3
4"""Tests for the Cython magics extension."""
5
6from __future__ import absolute_import
7
8import os
9import sys
10from contextlib import contextmanager
11from Cython.Build import IpythonMagic
12from Cython.TestUtils import CythonTest
13
14try:
15    import IPython.testing.globalipapp
16    from IPython.utils import py3compat
17except ImportError:
18    # Disable tests and fake helpers for initialisation below.
19    class _py3compat(object):
20        def str_to_unicode(self, s):
21            return s
22
23    py3compat = _py3compat()
24
25    def skip_if_not_installed(_):
26        return None
27else:
28    def skip_if_not_installed(c):
29        return c
30
31try:
32    # disable IPython history thread before it gets started to avoid having to clean it up
33    from IPython.core.history import HistoryManager
34    HistoryManager.enabled = False
35except ImportError:
36    pass
37
38code = py3compat.str_to_unicode("""\
39def f(x):
40    return 2*x
41""")
42
43cython3_code = py3compat.str_to_unicode("""\
44def f(int x):
45    return 2 / x
46
47def call(x):
48    return f(*(x,))
49""")
50
51pgo_cython3_code = cython3_code + py3compat.str_to_unicode("""\
52def main():
53    for _ in range(100): call(5)
54main()
55""")
56
57
58if sys.platform == 'win32':
59    # not using IPython's decorators here because they depend on "nose"
60    try:
61        from unittest import skip as skip_win32
62    except ImportError:
63        # poor dev's silent @unittest.skip()
64        def skip_win32(dummy):
65            def _skip_win32(func):
66                return None
67            return _skip_win32
68else:
69    def skip_win32(dummy):
70        def _skip_win32(func):
71            def wrapper(*args, **kwargs):
72                func(*args, **kwargs)
73            return wrapper
74        return _skip_win32
75
76
77@skip_if_not_installed
78class TestIPythonMagic(CythonTest):
79
80    @classmethod
81    def setUpClass(cls):
82        CythonTest.setUpClass()
83        cls._ip = IPython.testing.globalipapp.get_ipython()
84
85    def setUp(self):
86        CythonTest.setUp(self)
87        self._ip.extension_manager.load_extension('cython')
88
89    def test_cython_inline(self):
90        ip = self._ip
91        ip.ex('a=10; b=20')
92        result = ip.run_cell_magic('cython_inline', '', 'return a+b')
93        self.assertEqual(result, 30)
94
95    @skip_win32('Skip on Windows')
96    def test_cython_pyximport(self):
97        ip = self._ip
98        module_name = '_test_cython_pyximport'
99        ip.run_cell_magic('cython_pyximport', module_name, code)
100        ip.ex('g = f(10)')
101        self.assertEqual(ip.user_ns['g'], 20.0)
102        ip.run_cell_magic('cython_pyximport', module_name, code)
103        ip.ex('h = f(-10)')
104        self.assertEqual(ip.user_ns['h'], -20.0)
105        try:
106            os.remove(module_name + '.pyx')
107        except OSError:
108            pass
109
110    def test_cython(self):
111        ip = self._ip
112        ip.run_cell_magic('cython', '', code)
113        ip.ex('g = f(10)')
114        self.assertEqual(ip.user_ns['g'], 20.0)
115
116    def test_cython_name(self):
117        # The Cython module named 'mymodule' defines the function f.
118        ip = self._ip
119        ip.run_cell_magic('cython', '--name=mymodule', code)
120        # This module can now be imported in the interactive namespace.
121        ip.ex('import mymodule; g = mymodule.f(10)')
122        self.assertEqual(ip.user_ns['g'], 20.0)
123
124    def test_cython_language_level(self):
125        # The Cython cell defines the functions f() and call().
126        ip = self._ip
127        ip.run_cell_magic('cython', '', cython3_code)
128        ip.ex('g = f(10); h = call(10)')
129        if sys.version_info[0] < 3:
130            self.assertEqual(ip.user_ns['g'], 2 // 10)
131            self.assertEqual(ip.user_ns['h'], 2 // 10)
132        else:
133            self.assertEqual(ip.user_ns['g'], 2.0 / 10.0)
134            self.assertEqual(ip.user_ns['h'], 2.0 / 10.0)
135
136    def test_cython3(self):
137        # The Cython cell defines the functions f() and call().
138        ip = self._ip
139        ip.run_cell_magic('cython', '-3', cython3_code)
140        ip.ex('g = f(10); h = call(10)')
141        self.assertEqual(ip.user_ns['g'], 2.0 / 10.0)
142        self.assertEqual(ip.user_ns['h'], 2.0 / 10.0)
143
144    def test_cython2(self):
145        # The Cython cell defines the functions f() and call().
146        ip = self._ip
147        ip.run_cell_magic('cython', '-2', cython3_code)
148        ip.ex('g = f(10); h = call(10)')
149        self.assertEqual(ip.user_ns['g'], 2 // 10)
150        self.assertEqual(ip.user_ns['h'], 2 // 10)
151
152    @skip_win32('Skip on Windows')
153    def test_cython3_pgo(self):
154        # The Cython cell defines the functions f() and call().
155        ip = self._ip
156        ip.run_cell_magic('cython', '-3 --pgo', pgo_cython3_code)
157        ip.ex('g = f(10); h = call(10); main()')
158        self.assertEqual(ip.user_ns['g'], 2.0 / 10.0)
159        self.assertEqual(ip.user_ns['h'], 2.0 / 10.0)
160
161    @skip_win32('Skip on Windows')
162    def test_extlibs(self):
163        ip = self._ip
164        code = py3compat.str_to_unicode("""
165from libc.math cimport sin
166x = sin(0.0)
167        """)
168        ip.user_ns['x'] = 1
169        ip.run_cell_magic('cython', '-l m', code)
170        self.assertEqual(ip.user_ns['x'], 0)
171
172
173    def test_cython_verbose(self):
174        ip = self._ip
175        ip.run_cell_magic('cython', '--verbose', code)
176        ip.ex('g = f(10)')
177        self.assertEqual(ip.user_ns['g'], 20.0)
178
179    def test_cython_verbose_thresholds(self):
180        @contextmanager
181        def mock_distutils():
182            class MockLog:
183                DEBUG = 1
184                INFO = 2
185                thresholds = [INFO]
186
187                def set_threshold(self, val):
188                    self.thresholds.append(val)
189                    return self.thresholds[-2]
190
191
192            new_log = MockLog()
193            old_log = IpythonMagic.distutils.log
194            try:
195                IpythonMagic.distutils.log = new_log
196                yield new_log
197            finally:
198                IpythonMagic.distutils.log = old_log
199
200        ip = self._ip
201        with mock_distutils() as verbose_log:
202            ip.run_cell_magic('cython', '--verbose', code)
203            ip.ex('g = f(10)')
204        self.assertEqual(ip.user_ns['g'], 20.0)
205        self.assertEquals([verbose_log.INFO, verbose_log.DEBUG, verbose_log.INFO],
206                          verbose_log.thresholds)
207
208        with mock_distutils() as normal_log:
209            ip.run_cell_magic('cython', '', code)
210            ip.ex('g = f(10)')
211        self.assertEqual(ip.user_ns['g'], 20.0)
212        self.assertEquals([normal_log.INFO], normal_log.thresholds)
213