Skip to content

Commit 76f5d4f

Browse files
authored
Merge pull request #196 from bashtage/cython-coverage
BLD: Add coverage of Cython code
2 parents 3d3b390 + 1d456dd commit 76f5d4f

3 files changed

Lines changed: 66 additions & 72 deletions

File tree

.coveragerc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ include = */arch/*
66
omit =
77
*/_version.py
88
*/compat/*
9-
9+
plugins = Cython.Coverage
1010

1111
[report]
1212
# Regexes for lines to exclude from consideration
@@ -26,3 +26,4 @@ omit =
2626
*recursions.py
2727
*samplers.py
2828
ignore_errors = True
29+

.travis.yml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ matrix:
2828
- PYTHON=2.7
2929
- NUMBA=0.24
3030
- NUMPY=1.10
31-
- SCIPY=0.16
31+
- SCIPY=0.17
3232
- MATPLOTLIB=1.5
33-
- PANDAS=0.16
33+
- PANDAS=0.18
3434
- STATSMODELS_MASTER=true
3535
- python: 2.7
3636
env:
3737
- PYTHON=3.4
3838
- NUMPY=1.11
39-
- SCIPY=0.17
39+
- SCIPY=0.18
4040
- NUMBA=0.27
4141
- MATPLOTLIB=1.5
4242
- PANDAS=0.18
@@ -84,14 +84,15 @@ before_install:
8484
- conda list
8585
- export PYTHONHASHSEED=0
8686
- export MKL_NUM_THREADS=1
87+
- export ARCH_CYTHON_COVERAGE=${COVERAGE}
8788

8889
install:
89-
- python setup.py install
90+
- python setup.py develop
9091

9192
script:
9293
- set -e
9394
- flake8 arch
94-
- pytest -n 2 --cov-config .coveragerc --cov=arch arch --durations=10
95+
- pytest --cov-config .coveragerc --cov=arch arch --durations=10
9596
- python ci/performance.py
9697
- if [[ ${DOCBUILD} = true ]]; then cd doc && make html && cd .. ; fi
9798
- if [[ ${DOCBUILD} = true && ${TRAVIS_BRANCH} = "master" ]]; then doctr deploy doc; fi

setup.py

Lines changed: 58 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,27 @@
99
from distutils.version import StrictVersion
1010

1111
import pkg_resources
12-
from setuptools import setup, Extension, find_packages, Command
12+
import versioneer
13+
from Cython.Build import cythonize
14+
from setuptools import Command, Extension, find_packages, setup
1315
from setuptools.dist import Distribution
1416

15-
import versioneer
17+
CYTHON_COVERAGE = os.environ.get('ARCH_CYTHON_COVERAGE', '0') in ('true', '1', 'True')
18+
if CYTHON_COVERAGE:
19+
print('Building with coverage for cython modules, ARCH_CYTHON_COVERAGE=' +
20+
os.environ['ARCH_CYTHON_COVERAGE'])
1621

1722
try:
1823
from Cython.Distutils.build_ext import build_ext as _build_ext
19-
24+
2025
CYTHON_INSTALLED = True
2126
except ImportError:
2227
CYTHON_INSTALLED = False
23-
24-
28+
if CYTHON_COVERAGE:
29+
raise ImportError('cython is required for cython coverage. Unset '
30+
'ARCH_CYTHON_COVERAGE')
31+
32+
2533
class _build_ext(object):
2634
pass
2735

@@ -47,7 +55,7 @@ class _build_ext(object):
4755
class build_ext(_build_ext):
4856
def build_extensions(self):
4957
numpy_incl = pkg_resources.resource_filename('numpy', 'core/include')
50-
58+
5159
for ext in self.extensions:
5260
if (hasattr(ext, 'include_dirs') and
5361
numpy_incl not in ext.include_dirs):
@@ -59,17 +67,12 @@ def build_extensions(self):
5967
REQUIREMENTS = {'Cython': '0.24',
6068
'matplotlib': '1.5',
6169
'scipy': '0.16',
62-
'pandas': '0.16',
63-
'statsmodels': '0.6'}
70+
'pandas': '0.18',
71+
'statsmodels': '0.8'}
6472

6573
ALL_REQUIREMENTS = SETUP_REQUIREMENTS.copy()
6674
ALL_REQUIREMENTS.update(REQUIREMENTS)
6775

68-
ext_modules = []
69-
ext_modules.append(Extension("arch.univariate.recursions",
70-
["./arch/univariate/recursions.pyx"]))
71-
ext_modules.append(Extension("arch.bootstrap._samplers",
72-
["./arch/bootstrap/_samplers.pyx"]))
7376
cmdclass['build_ext'] = build_ext
7477

7578

@@ -79,44 +82,17 @@ def is_pure(self):
7982

8083

8184
class CleanCommand(Command):
82-
"""Custom distutils command to clean the .so and .pyc files."""
83-
84-
user_options = [("all", "a", "")]
85-
85+
user_options = []
86+
87+
def run(self):
88+
raise NotImplementedError('Use git clean -xfd instead')
89+
8690
def initialize_options(self):
87-
self.all = True
88-
self._clean_files = []
89-
self._clean_trees = []
90-
for root, dirs, files in list(os.walk('arch')):
91-
for f in files:
92-
if os.path.splitext(f)[-1] == '.pyx':
93-
search = os.path.join(root, os.path.splitext(f)[0] + '.*')
94-
candidates = glob.glob(search)
95-
for c in candidates:
96-
if os.path.splitext(c)[-1] in ('.pyc', '.c', '.so',
97-
'.pyd', '.dll'):
98-
self._clean_files.append(c)
99-
100-
for d in ('build',):
101-
if os.path.exists(d):
102-
self._clean_trees.append(d)
103-
91+
pass
92+
10493
def finalize_options(self):
10594
pass
10695

107-
def run(self):
108-
for f in self._clean_files:
109-
try:
110-
os.unlink(f)
111-
except Exception:
112-
pass
113-
for clean_tree in self._clean_trees:
114-
try:
115-
import shutil
116-
shutil.rmtree(clean_tree)
117-
except Exception:
118-
pass
119-
12096

12197
cmdclass['clean'] = CleanCommand
12298

@@ -141,25 +117,25 @@ def strip_rc(version):
141117
if key == 'numpy':
142118
try:
143119
import numpy
144-
120+
145121
try:
146122
from numpy.version import short_version as version
147123
except ImportError:
148124
satisfies_req = False
149125
except ImportError:
150126
pass
151-
127+
152128
elif key == 'scipy':
153129
try:
154130
import scipy
155-
131+
156132
try:
157133
from scipy.version import short_version as version
158134
except ImportError:
159135
satisfies_req = False
160136
except ImportError:
161137
pass
162-
138+
163139
elif key == 'pandas':
164140
try:
165141
from pandas.version import short_version as version
@@ -169,7 +145,7 @@ def strip_rc(version):
169145
satisfies_req = False
170146
else:
171147
raise NotImplementedError('Unknown package')
172-
148+
173149
if version:
174150
existing_version = StrictVersion(strip_rc(version))
175151
satisfies_req = existing_version >= ALL_REQUIREMENTS[key]
@@ -191,7 +167,7 @@ def strip_rc(version):
191167
long_description = open(os.path.join(cwd, "README.rst")).read()
192168
except IOError as e:
193169
import warnings
194-
170+
195171
warnings.warn('Unable to convert README.md. Most likely because pandoc '
196172
'is not installed')
197173

@@ -205,66 +181,82 @@ def strip_rc(version):
205181
try:
206182
import nbformat as nbformat
207183
from nbconvert import RSTExporter
208-
184+
209185
notebooks = glob.glob(os.path.join(cwd, 'examples', '*.ipynb'))
210186
for notebook in notebooks:
211187
try:
212188
with open(notebook, 'rt') as f:
213189
example_nb = f.read()
214-
190+
215191
rst_path = os.path.join(cwd, 'doc', 'source')
216192
path_parts = os.path.split(notebook)
217193
nb_filename = path_parts[-1]
218194
nb_filename = nb_filename.split('.')[0]
219195
source_dir = nb_filename.split('_')[0]
220196
rst_filename = os.path.join(cwd, 'doc', 'source',
221197
source_dir, nb_filename + '.rst')
222-
198+
223199
example_nb = nbformat.reader.reads(example_nb)
224200
rst_export = RSTExporter()
225201
(body, resources) = rst_export.from_notebook_node(example_nb)
226202
with open(rst_filename, 'wt') as rst:
227203
rst.write(body)
228-
204+
229205
for key in resources['outputs'].keys():
230206
if key.endswith('.png'):
231207
resource_filename = os.path.join(cwd, 'doc', 'source',
232208
source_dir, key)
233209
with open(resource_filename, 'wb') as resource:
234210
resource.write(resources['outputs'][key])
235-
211+
236212
except:
237213
import warnings
238-
214+
239215
warnings.warn('Unable to convert {original} to {target}. This '
240216
'only affects documentation generation and not the '
241217
'operation of the '
242218
'module.'.format(original=notebook,
243219
target=rst_filename))
244220
print('The last error was:')
245221
import sys
246-
222+
247223
print(sys.exc_info()[0])
248224
print(sys.exc_info()[1])
249225

250226
except:
251227
import warnings
252-
228+
253229
warnings.warn('Unable to import required modules from the jupyter project.'
254230
' This only affects documentation generation and not the '
255231
'operation of the module.')
256232
print('The last error was:')
257233
import sys
258-
234+
259235
print(sys.exc_info()[0])
260236
print(sys.exc_info()[1])
261237

262238

263239
def run_setup(binary=True):
264-
extensions = ext_modules if binary else []
265240
if not binary:
266241
del REQUIREMENTS['Cython']
267-
242+
extensions = []
243+
else:
244+
directives = {'linetrace': CYTHON_COVERAGE}
245+
macros = []
246+
if CYTHON_COVERAGE:
247+
macros.append(('CYTHON_TRACE', '1'))
248+
249+
ext_modules = []
250+
ext_modules.append(Extension("arch.univariate.recursions",
251+
["./arch/univariate/recursions.pyx"],
252+
define_macros=macros))
253+
ext_modules.append(Extension("arch.bootstrap._samplers",
254+
["./arch/bootstrap/_samplers.pyx"],
255+
define_macros=macros))
256+
extensions = cythonize(ext_modules,
257+
force=CYTHON_COVERAGE,
258+
compiler_directives=directives)
259+
268260
setup(name='arch',
269261
license='NCSA',
270262
version=versioneer.get_version(),
@@ -310,10 +302,10 @@ def run_setup(binary=True):
310302
build_binary = '--no-binary' not in sys.argv and CYTHON_INSTALLED
311303
if '--no-binary' in sys.argv:
312304
sys.argv.remove('--no-binary')
313-
305+
314306
run_setup(binary=build_binary)
315307
except (CCompilerError, DistutilsExecError, DistutilsPlatformError, IOError, ValueError):
316308
run_setup(binary=False)
317309
import warnings
318-
310+
319311
warnings.warn(FAILED_COMPILER_ERROR, UserWarning)

0 commit comments

Comments
 (0)