diff --git a/.coveragerc b/.coveragerc index eae002657d..3209762b44 100644 --- a/.coveragerc +++ b/.coveragerc @@ -7,5 +7,6 @@ omit = */vispy/gloo/gl/angle.py */vispy/gloo/gl/_angle.py */vispy/testing/_runners.py + */vispy/testing/_coverage.py */vispy/ext/*.py */examples/* diff --git a/.gitignore b/.gitignore index 111c18abc2..f8b69c9092 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ build dist MANIFEST .coverage +htmlcov vispy.egg-info diff --git a/Makefile b/Makefile index c71b123097..099f7fff5a 100755 --- a/Makefile +++ b/Makefile @@ -5,24 +5,26 @@ CTAGS ?= ctags all: clean inplace test clean-pyc: - find . -name "*.pyc" | xargs rm -f + @find . -name "*.pyc" | xargs rm -f clean-so: - find . -name "*.so" | xargs rm -f - find . -name "*.pyd" | xargs rm -f + @find . -name "*.so" | xargs rm -f + @find . -name "*.pyd" | xargs rm -f clean-build: - rm -rf build + @rm -rf build clean-ctags: - rm -f tags + @rm -f tags clean-cache: - find . -name "__pycache__" | xargs rm -rf + @find . -name "__pycache__" | xargs rm -rf clean: clean-build clean-pyc clean-so clean-ctags clean-cache + @echo "Cleaning build, pyc, so, ctags, and cache" clean-test: clean-build clean-pyc clean-ctags clean-cache + @echo "Cleaning build, pyc, ctags, and cache" in: inplace # just a shortcut inplace: @@ -38,6 +40,9 @@ nose: clean-test nose_coverage: clean-test python make test nose 1 +coverage_html: + python make coverage_html + test: clean-test python make test full diff --git a/make/make.py b/make/make.py index e7205c8841..e23b8b8ac8 100644 --- a/make/make.py +++ b/make/make.py @@ -14,34 +14,36 @@ import sys import os +from os import path as op import time import shutil import subprocess import re +import webbrowser # Save where we came frome and where this module lives -START_DIR = os.path.abspath(os.getcwd()) -THIS_DIR = os.path.abspath(os.path.dirname(__file__)) +START_DIR = op.abspath(os.getcwd()) +THIS_DIR = op.abspath(op.dirname(__file__)) # Get root directory of the package, by looking for setup.py for subdir in ['.', '..']: - ROOT_DIR = os.path.abspath(os.path.join(THIS_DIR, subdir)) - if os.path.isfile(os.path.join(ROOT_DIR, 'setup.py')): + ROOT_DIR = op.abspath(op.join(THIS_DIR, subdir)) + if op.isfile(op.join(ROOT_DIR, 'setup.py')): break else: sys.exit('Cannot find root dir') # Define directories and repos of interest -DOC_DIR = os.path.join(ROOT_DIR, 'doc') +DOC_DIR = op.join(ROOT_DIR, 'doc') # -WEBSITE_DIR = os.path.join(ROOT_DIR, '_website') +WEBSITE_DIR = op.join(ROOT_DIR, '_website') WEBSITE_REPO = 'git@github.com:vispy/vispy-website' # -PAGES_DIR = os.path.join(ROOT_DIR, '_gh-pages') +PAGES_DIR = op.join(ROOT_DIR, '_gh-pages') PAGES_REPO = 'git@github.com:vispy/vispy.github.com.git' # -IMAGES_DIR = os.path.join(ROOT_DIR, '_images') +IMAGES_DIR = op.join(ROOT_DIR, '_images') IMAGES_REPO = 'git@github.com:vispy/images.git' @@ -69,6 +71,17 @@ def __init__(self, argv): else: sys.exit('Invalid command: "%s"' % command) + def coverage_html(self, arg): + """Generate html report from .coverage and launch""" + print('Generating HTML...') + from coverage import coverage + cov = coverage(auto_data=False, branch=True, data_suffix=None, + source=['vispy']) # should match testing/_coverage.py + cov.load() + cov.html_report() + print('Done, launching browser.') + webbrowser.open_new_tab(op.join(os.getcwd(), 'htmlcov', 'index.html')) + def help(self, arg): """ Show help message. Use 'help X' to get more help on command X. """ if arg: @@ -87,7 +100,7 @@ def help(self, arg): if command.startswith('_'): continue preamble = command.ljust(11) # longest command is 9 or 10 - #doc = getattr(self, command).__doc__.splitlines()[0].strip() + # doc = getattr(self, command).__doc__.splitlines()[0].strip() doc = getattr(self, command).__doc__.strip() print(' %s %s' % (preamble, doc)) print() @@ -98,7 +111,7 @@ def doc(self, arg): * show - show the docs in your browser """ # Prepare - build_dir = os.path.join(DOC_DIR, '_build') + build_dir = op.join(DOC_DIR, '_build') if not arg: return self.help('doc') # Go @@ -106,7 +119,7 @@ def doc(self, arg): sphinx_clean(build_dir) sphinx_build(DOC_DIR, build_dir) elif 'show' == arg: - sphinx_show(os.path.join(build_dir, 'html')) + sphinx_show(op.join(build_dir, 'html')) else: sys.exit('Command "doc" does not have subcommand "%s"' % arg) @@ -117,11 +130,11 @@ def website(self, arg): * upload - upload (commit+push) the resulting html to github """ # Prepare - build_dir = os.path.join(WEBSITE_DIR, '_build') - html_dir = os.path.join(build_dir, 'html') - + build_dir = op.join(WEBSITE_DIR, '_build') + html_dir = op.join(build_dir, 'html') + # Clone repo for website if needed, make up-to-date otherwise - if not os.path.isdir(WEBSITE_DIR): + if not op.isdir(WEBSITE_DIR): os.chdir(ROOT_DIR) sh("git clone %s %s" % (WEBSITE_REPO, WEBSITE_DIR)) else: @@ -131,7 +144,7 @@ def website(self, arg): if not arg: return self.help('website') - + # Go if 'html' == arg: sphinx_clean(build_dir) @@ -173,23 +186,23 @@ def images(self, arg): * test - make screenshots for testing * upload - upload the images repository """ - + # Clone repo for images if needed, make up-to-date otherwise - if not os.path.isdir(IMAGES_DIR): + if not op.isdir(IMAGES_DIR): os.chdir(ROOT_DIR) sh("git clone %s %s" % (IMAGES_REPO, IMAGES_DIR)) else: print('Updating images repo') os.chdir(IMAGES_DIR) sh('git pull') - + if not arg: return self.help('images') - + # Create subdirs if needed for subdir in ['gallery', 'thumbs', 'test']: - subdir = os.path.join(IMAGES_DIR, subdir) - if not os.path.isdir(subdir): + subdir = op.join(IMAGES_DIR, subdir) + if not op.isdir(subdir): os.mkdir(subdir) # Go @@ -208,13 +221,13 @@ def _images_screenshots(self): import imp from vispy.util.dataio import imsave from vispy.gloo import _screenshot - examples_dir = os.path.join(ROOT_DIR, 'examples') - gallery_dir = os.path.join(IMAGES_DIR, 'gallery') + examples_dir = op.join(ROOT_DIR, 'examples') + gallery_dir = op.join(IMAGES_DIR, 'gallery') # Process all files ... for filename, name in get_example_filenames(examples_dir): name = name.replace('/', '__') # We use flat names - imagefilename = os.path.join(gallery_dir, name + '.png') + imagefilename = op.join(gallery_dir, name + '.png') # Check if should make a screenshot frames = [] @@ -234,7 +247,7 @@ def _images_screenshots(self): continue # gallery hint not found # Check if we need to take a sceenshot - if os.path.isfile(imagefilename): + if op.isfile(imagefilename): print('Screenshot for %s already present (skip).' % name) continue @@ -273,11 +286,11 @@ def _images_thumbnails(self): from vispy.util.dataio import imsave, imread from skimage.transform import resize import numpy as np - gallery_dir = os.path.join(IMAGES_DIR, 'gallery') - thumbs_dir = os.path.join(IMAGES_DIR, 'thumbs') + gallery_dir = op.join(IMAGES_DIR, 'gallery') + thumbs_dir = op.join(IMAGES_DIR, 'thumbs') for fname in os.listdir(gallery_dir): - filename1 = os.path.join(gallery_dir, fname) - filename2 = os.path.join(thumbs_dir, fname) + filename1 = op.join(gallery_dir, fname) + filename2 = op.join(thumbs_dir, fname) # im = imread(filename1) newx = 200 @@ -300,17 +313,17 @@ def copyright(self, arg): # Processing the whole root directory for dirpath, dirnames, filenames in os.walk(ROOT_DIR): # Check if we should skip this directory - reldirpath = os.path.relpath(dirpath, ROOT_DIR) + reldirpath = op.relpath(dirpath, ROOT_DIR) if reldirpath[0] in '._' or reldirpath.endswith('__pycache__'): continue - if os.path.split(reldirpath)[0] in ('build', 'dist'): + if op.split(reldirpath)[0] in ('build', 'dist'): continue # Process files for fname in filenames: if not fname.endswith('.py'): continue # Open and check - filename = os.path.join(dirpath, fname) + filename = op.join(dirpath, fname) text = open(filename, 'rt').read() if NEWTEXT in text: count_ok += 1 @@ -354,7 +367,7 @@ def sh2(cmd): def sphinx_clean(build_dir): - if os.path.isdir(build_dir): + if op.isdir(build_dir): shutil.rmtree(build_dir) os.mkdir(build_dir) print('Cleared build directory.') @@ -364,16 +377,16 @@ def sphinx_build(src_dir, build_dir): import sphinx sphinx.main(('sphinx-build', # Dummy '-b', 'html', - '-d', os.path.join(build_dir, 'doctrees'), + '-d', op.join(build_dir, 'doctrees'), src_dir, # Source - os.path.join(build_dir, 'html'), # Dest + op.join(build_dir, 'html'), # Dest )) print("Build finished. The HTML pages are in %s/html." % build_dir) def sphinx_show(html_dir): - index_html = os.path.join(html_dir, 'index.html') - if not os.path.isfile(index_html): + index_html = op.join(html_dir, 'index.html') + if not op.isfile(index_html): sys.exit('Cannot show pages, build the html first.') import webbrowser webbrowser.open_new_tab(index_html) @@ -381,7 +394,7 @@ def sphinx_show(html_dir): def sphinx_copy_pages(html_dir, pages_dir, pages_repo): # Create the pages repo if needed - if not os.path.isdir(pages_dir): + if not op.isdir(pages_dir): os.chdir(ROOT_DIR) sh("git clone %s %s" % (pages_repo, pages_dir)) # Ensure that its up to date @@ -390,19 +403,19 @@ def sphinx_copy_pages(html_dir, pages_dir, pages_repo): sh('git pull -q') # This is pretty unforgiving: we unconditionally nuke the destination # directory, and then copy the html tree in there - tmp_git_dir = os.path.join(ROOT_DIR, pages_dir + '_git') - shutil.move(os.path.join(pages_dir, '.git'), tmp_git_dir) + tmp_git_dir = op.join(ROOT_DIR, pages_dir + '_git') + shutil.move(op.join(pages_dir, '.git'), tmp_git_dir) try: shutil.rmtree(pages_dir) shutil.copytree(html_dir, pages_dir) - shutil.move(tmp_git_dir, os.path.join(pages_dir, '.git')) + shutil.move(tmp_git_dir, op.join(pages_dir, '.git')) finally: - if os.path.isdir(tmp_git_dir): + if op.isdir(tmp_git_dir): shutil.rmtree(tmp_git_dir) # Copy individual files for fname in ['CNAME', 'README.md', 'conf.py', '.nojekyll', 'Makefile']: - shutil.copyfile(os.path.join(WEBSITE_DIR, fname), - os.path.join(pages_dir, fname)) + shutil.copyfile(op.join(WEBSITE_DIR, fname), + op.join(pages_dir, fname)) # Messages os.chdir(pages_dir) sh('git status') @@ -448,7 +461,7 @@ def get_example_filenames(example_dir): for fname in filenames: if not fname.endswith('.py'): continue - filename = os.path.join(dirpath, fname) + filename = op.join(dirpath, fname) name = filename[len(example_dir):].lstrip('/\\')[:-3] name = name.replace('\\', '/') yield filename, name diff --git a/vispy/app/tests/test_app.py b/vispy/app/tests/test_app.py index f95dc81500..69612ac714 100644 --- a/vispy/app/tests/test_app.py +++ b/vispy/app/tests/test_app.py @@ -204,7 +204,6 @@ def test_application(): assert_raises(ValueError, canvas.connect, on_nonexist) # deprecation of "paint" with use_log_level('info', record=True) as log: - x = [] olderr = sys.stderr try: with open(os.devnull, 'w') as fid: @@ -212,15 +211,11 @@ def test_application(): @canvas.events.paint.connect def fake(event): - x.append(0) + pass finally: sys.stderr = olderr - canvas.update() - canvas.app.process_events() assert_equal(len(log), 1) assert_in('deprecated', log[0]) - if app.backend_name.lower() != 'glut': # XXX knownfail - assert_true(len(x) >= 1) # screenshots gl.glViewport(0, 0, *size) diff --git a/vispy/app/tests/test_qt.py b/vispy/app/tests/test_qt.py index b9ea9a77cd..0d74168e4e 100644 --- a/vispy/app/tests/test_qt.py +++ b/vispy/app/tests/test_qt.py @@ -4,6 +4,7 @@ # This is a strange test: vispy does not need designer or uic stuff to run! from os import path as op +import warnings from vispy.app import Canvas, use_app from vispy.testing import requires_application, SkipTest @@ -18,7 +19,8 @@ def test_qt_designer(): raise SkipTest('Not using PyQt4 backend') # wrong backend from PyQt4 import uic fname = op.join(op.dirname(__file__), 'qt-designer.ui') - WindowTemplate, TemplateBaseClass = uic.loadUiType(fname) + with warnings.catch_warnings(record=True): # pyqt4 deprecation warning + WindowTemplate, TemplateBaseClass = uic.loadUiType(fname) app.create() # make sure we have an app, or the init will fail class MainWindow(TemplateBaseClass): diff --git a/vispy/testing/_coverage.py b/vispy/testing/_coverage.py new file mode 100644 index 0000000000..50a62372d5 --- /dev/null +++ b/vispy/testing/_coverage.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2014, Vispy Development Team. +# Distributed under the (new) BSD License. See LICENSE.txt for more info. + +# Code inspired by original nose plugin: +# https://nose.readthedocs.org/en/latest/plugins/cover.html + +from nose.plugins.base import Plugin +from coverage import coverage + + +class MutedCoverage(Plugin): + """Make a silent coverage report using Ned Batchelder's coverage module.""" + + def configure(self, options, conf): + Plugin.configure(self, options, conf) + self.enabled = True + self.cov = coverage(auto_data=False, branch=True, data_suffix=None, + source=['vispy']) + + def begin(self): + self.cov.load() + self.cov.start() + + def report(self, stream): + self.cov.stop() + self.cov.combine() + self.cov.save() diff --git a/vispy/testing/_runners.py b/vispy/testing/_runners.py index 3541823ae8..a3cb54e265 100644 --- a/vispy/testing/_runners.py +++ b/vispy/testing/_runners.py @@ -1,3 +1,6 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2014, Vispy Development Team. +# Distributed under the (new) BSD License. See LICENSE.txt for more info. """Test running functions""" from __future__ import print_function @@ -47,15 +50,22 @@ def _nose(mode, verbosity, coverage): print(extra + '\n' + msg + '\n' + extra + '\n') # last \n nicer raise SkipTest(msg) sys.stdout.flush() - if coverage: - covs = '--with-coverage --cover-package=vispy --cover-branches ' - else: - covs = '' - args = ' ' + ('--verbosity=%s ' % verbosity) + covs + attrs + # we might as well always use coverage, since we manually disable printing! + # here we actually read in the Python code to avoid importing it from + # from vispy.testing._coverage, since doing so breaks some path stuff later + muted_file = op.join(op.dirname(__file__), '_coverage.py') + with open(muted_file, 'r') as fid: + imps = fid.read() + cv = ', addplugins=[MutedCoverage()]' + # if not coverage: + # imps = '' + # cv = '' + arg = ' ' + ('--verbosity=%s ' % verbosity) + attrs # make a call to "python" so that it inherits whatever the system # thinks is "python" (e.g., virtualenvs) cmd = [sys.executable, '-c', - 'import nose; nose.main(argv="%s".split(" "))' % args] + '%simport nose; nose.main(argv="%s".split(" ")%s)' + % (imps, arg, cv)] env = deepcopy(os.environ) env.update(dict(_VISPY_TESTING_TYPE=mode)) p = Popen(cmd, cwd=cwd, env=env)