PYTHON setup.py build_ext --inplace
PYTHON test_profile.py

######## setup.py ###########

from distutils.extension import Extension
from distutils.core import setup
from Cython.Build import cythonize

extensions = [
    Extension("collatz", ["collatz.pyx"], define_macros=[('CYTHON_TRACE', '1')])
]

setup(
    ext_modules = cythonize(extensions)
)

######## test_profile.py ###########

try:
    import line_profiler
except ImportError:
    print("No line profiler, skipping test.")
    import sys
    sys.exit(0)


def assert_stats(profile, name):
    profile.print_stats()
    stats = profile.get_stats()
    assert len(stats.timings) > 0, "No profile stats."
    for key, timings in stats.timings.items():
        if key[-1] == name:
            assert len(timings) > 0
            break
    else:
        raise ValueError("No stats for %s." % name)


from collatz import collatz
func = collatz
profile = line_profiler.LineProfiler(func)
profile.runcall(func, 19)
assert_stats(profile, func.__name__)

from collatz import cp_collatz
func = cp_collatz
profile = line_profiler.LineProfiler(func)
profile.runcall(func, 19)
assert_stats(profile, func.__name__)

from collatz import run_generator, cy_generator
func = cy_generator
profile = line_profiler.LineProfiler(func)
profile.runcall(run_generator, 19)
assert_stats(profile, func.__name__)

from collatz import run_coro, cy_coro
func = cy_coro
profile = line_profiler.LineProfiler(func)
profile.runcall(run_coro, 19)
assert_stats(profile, func.__name__)

from collatz import PyClass
obj = PyClass()
func = obj.py_pymethod
profile = line_profiler.LineProfiler(func)
profile.runcall(func)
assert_stats(profile, func.__name__)

from collatz import CClass
obj = CClass()
func = obj.c_pymethod
profile = line_profiler.LineProfiler(func)
profile.runcall(func)
assert_stats(profile, func.__name__)

func = obj.cp_pymethod
profile = line_profiler.LineProfiler(func)
profile.runcall(func, 19)
assert_stats(profile, func.__name__)


######## collatz.pyx ###########
# cython: linetrace=True

cimport cython

@cython.binding(True)
def collatz(n):
    while n > 1:
        if n % 2 == 0:
            n //= 2
        else:
            n = 3*n+1


@cython.binding(True)
cpdef cp_collatz(n):
    while n > 1:
        if n % 2 == 0:
            n //= 2
        else:
            n = 3*n+1


@cython.binding(True)
def cy_generator(int n):
   x = 1
   for i in range(n):
       yield x + 2


@cython.binding(True)
def run_generator(n):
    assert len(list(cy_generator(n))) == n


@cython.binding(True)
async def cy_coro(int n):
    while n > 1:
        if n % 2 == 0:
            n //= 2
        else:
            n = 3*n+1


@cython.binding(True)
def run_coro(n):
    coro = cy_coro(n)
    try:
        coro.send(None)
    except StopIteration:
        assert True
    else:
        assert False, "Coroutine did not raise"


@cython.binding(True)
class PyClass(object):
    def py_pymethod(self):
        x = 1
        for i in range(10):
            a = x + 2
        return a * 3


@cython.binding(True)
cdef class CClass:
    def c_pymethod(self, c=2):
        for i in range(10):
            a = c + 1
        y = self.cmethod(c + a)
        return y * 4

    cpdef cp_pymethod(self, r):
        for i in range(10):
            a = r + 1
        z = self.c_pymethod(a) + self.cmethod(r)
        return z * 2

    cdef cmethod(self, s):
        for i in range(10):
            p = s + 3
        return p * 5
