# mode: run
# tag: coverage,trace

"""
PYTHON -c "import shutil; shutil.copy('pkg/coverage_test_pyx.pyx', 'pkg/coverage_test_pyx.pxi')"
PYTHON setup.py build_ext -i
PYTHON coverage_test.py
"""

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

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

setup(ext_modules = cythonize([
    'coverage_test_*.py*',
    'pkg/coverage_test_*.py*',
    'Package2/CoverageTest_*.py*'
]))


######## .coveragerc ########
[run]
plugins = Cython.Coverage


######## pkg/__init__.py ########

######## pkg/coverage_test_py.py ########
# cython: linetrace=True
# distutils: define_macros=CYTHON_TRACE=1

def func1(a, b):
    x = 1               #  5
    c = func2(a) + b    #  6
    return x + c        #  7


def func2(a):
    return a * 2        # 11


######## pkg/coverage_test_pyx.pyx ########
# cython: linetrace=True
# distutils: define_macros=CYTHON_TRACE=1

def func1(int a, int b):
    cdef int x = 1      #  5
    c = func2(a) + b    #  6
    return x + c        #  7


def func2(int a):
    return a * 2        # 11


######## coverage_test_include_pyx.pyx ########
# cython: linetrace=True
# distutils: define_macros=CYTHON_TRACE=1

cdef int x = 5                                   #  4

cdef int cfunc1(int x):                          #  6
    return x * 3                                 #  7

include "pkg/coverage_test_pyx.pxi"              #  9

def main_func(int x):                            # 11
    return cfunc1(x) + func1(x, 4) + func2(x)    # 12


######## Package2/__init__.py ########
# Add MixedCase package and filenames to test if the files are found

######## Package2/CoverageTest_py.py ########
# cython: linetrace=True
# distutils: define_macros=CYTHON_TRACE=1

def func1(a, b):
    x = 1               #  5
    c = func2(a) + b    #  6
    return x + c        #  7


def func2(a):
    return a * 2        # 11


######## Package2/CoverageTest_pyx.pyx ########
# cython: linetrace=True
# distutils: define_macros=CYTHON_TRACE=1

def func1(int a, int b):
    cdef int x = 1      #  5
    c = func2(a) + b    #  6
    return x + c        #  7


def func2(int a):
    return a * 2        # 11


######## coverage_test_include_pyx.pyx ########
# cython: linetrace=True
# distutils: define_macros=CYTHON_TRACE=1

cdef int x = 5                                   #  4

cdef int cfunc1(int x):                          #  6
    return x * 3                                 #  7

include "pkg/coverage_test_pyx.pxi"              #  9

def main_func(int x):                            # 11
    return cfunc1(x) + func1(x, 4) + func2(x)    # 12


######## coverage_test.py ########

import re
import os.path
try:
    # io.StringIO in Py2.x cannot handle str ...
    from StringIO import StringIO
except ImportError:
    from io import StringIO

from coverage import coverage

from pkg import coverage_test_py
from pkg import coverage_test_pyx
import coverage_test_include_pyx

# test the MixedCase Files and packages
from Package2 import CoverageTest_py
from Package2 import CoverageTest_pyx

for module in [coverage_test_py, coverage_test_pyx, coverage_test_include_pyx,
               CoverageTest_py, CoverageTest_pyx]:
    assert not any(module.__file__.endswith(ext) for ext in '.py .pyc .pyo .pyw .pyx .pxi'.split()), \
        module.__file__


def source_file_for(module):
    module_name = module.__name__
    path, ext = os.path.splitext(module.__file__)
    if ext == '.so':
        # Linux/Unix/Mac extension module
        platform_suffix = re.search(r'[.](?:cpython|pypy)-[0-9]+[-_a-z0-9]*$', path, re.I)
        if platform_suffix:
            path = path[:platform_suffix.start()]
    elif ext == '.pyd':
        # Windows extension module
        platform_suffix = re.search(r'[.]cp[0-9]+-win[_a-z0-9]*$', path, re.I)
        if platform_suffix:
            path = path[:platform_suffix.start()]
    source_filepath = path + '.' + module_name.rsplit('_', 1)[-1]
    return source_filepath


def run_coverage(module):
    module_name = module.__name__
    module_path = module_name.replace('.', os.path.sep) + '.' + module_name.rsplit('_', 1)[-1]

    cov = coverage()
    cov.start()
    assert module.func1(1, 2) == (1 * 2) + 2 + 1
    assert module.func2(2) == 2 * 2
    if '_include_' in module_name:
        assert module.main_func(2) == (2 * 3) + ((2 * 2) + 4 + 1) + (2 * 2)
    cov.stop()

    out = StringIO()
    cov.report(file=out)
    #cov.report([module], file=out)
    lines = out.getvalue().splitlines()
    assert any(module_path in line for line in lines), "'%s' not found in coverage report:\n\n%s" % (
        module_path, out.getvalue())

    mod_file, exec_lines, excl_lines, missing_lines, _ = cov.analysis2(source_file_for(module))
    assert module_path in mod_file

    if '_include_' in module_name:
        executed = set(exec_lines) - set(missing_lines)
        assert all(line in executed for line in [7, 12]), '%s / %s' % (exec_lines, missing_lines)

        # rest of test if for include file
        mod_file, exec_lines, excl_lines, missing_lines, _ = cov.analysis2(
            os.path.join(os.path.dirname(module.__file__), "pkg", "coverage_test_pyx.pxi"))

    executed = set(exec_lines) - set(missing_lines)
    assert all(line in executed for line in [5, 6, 7, 11]), '%s / %s' % (exec_lines, missing_lines)


if __name__ == '__main__':
    run_coverage(coverage_test_py)
    run_coverage(coverage_test_pyx)
    run_coverage(coverage_test_include_pyx)
    run_coverage(CoverageTest_py)
    run_coverage(CoverageTest_pyx)
