diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce8563b --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.pyc +*~ +\#*# +.#* +._* +build +dist +*.egg-info diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..46307a4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2012, Ryan Kaskel +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..9d5d250 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include LICENSE +include README.rst diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..d825ba1 --- /dev/null +++ b/README.rst @@ -0,0 +1,47 @@ +django-discoverage +================== + +Adds `coverage `_ to Jannis Leidel and +Carl Meyer's `django-discover-runner +`_. + +Inspired by `django-coverage `_. + +Usage +----- + +One of the objectives of ``django-discover-runner`` is to allow separating a +Django app's tests from the code it's testing. Since tests no longer reside in +an app, ``django-discoverage`` needs a different way to know which apps to +include in the coverage report. It currently collects apps (packages) by looking +for an attribute (by default ``TESTS_APPS``) on each ``TestCase`` instance in +the suite. + +For example:: + + class MyTestCase(TestCase): + TESTS_APPS = ('mycoolapp', 'myproject.anothercoolapp') + ... + +All modules in the listed apps (except those specified in ``OMIT_MODULES``) will +appear in the standard ``coverage`` report. + +Settings +-------- + +``APPS_TEST_CASE_ATTR`` + The attribute ``django-discoverage`` looks for on each ``TestCase`` instance. + +``OMIT_MODULES`` + Modules not to be traced by ``coverage``. See the `coverage API + documentation + `_ for more + details. + +TODO +---- + +* Investigate discovering the apps being tested from the test modules imports + (this could be really annoying) +* Check whether the report can be customized to display package names instead of + paths diff --git a/discoverage/__init__.py b/discoverage/__init__.py new file mode 100644 index 0000000..de959f0 --- /dev/null +++ b/discoverage/__init__.py @@ -0,0 +1,3 @@ +from discoverage.runner import DiscoverageRunner + +__version__ = '0.1.0' diff --git a/discoverage/runner.py b/discoverage/runner.py new file mode 100644 index 0000000..6ac3bd4 --- /dev/null +++ b/discoverage/runner.py @@ -0,0 +1,33 @@ +import coverage +from discover_runner import DiscoverRunner + +from discoverage.settings import OMIT_MODULES, APPS_TEST_CASE_ATTR + +def find_coverage_apps(suite): + coverage_apps = set() + for test in suite: + apps = getattr(test, APPS_TEST_CASE_ATTR, ()) + for app in apps: + coverage_apps.add(app) + return coverage_apps + + +class DiscoverageRunner(DiscoverRunner): + def build_suite(self, *args, **kwargs): + if not hasattr(self, '_suite'): + self._suite = super(DiscoverageRunner, self).build_suite( + *args, **kwargs) + return self._suite + + def run_tests(self, test_labels, extra_tests=None, **kwargs): + suite = self.build_suite(test_labels, extra_tests) + coverage_modules = find_coverage_apps(suite) + cov = coverage.coverage(source=coverage_modules, omit=OMIT_MODULES) + cov.start() + result = super(DiscoverageRunner, self).run_tests(test_labels, + extra_tests=None, + **kwargs) + cov.stop() + print + cov.report() + return result diff --git a/discoverage/settings.py b/discoverage/settings.py new file mode 100644 index 0000000..d338698 --- /dev/null +++ b/discoverage/settings.py @@ -0,0 +1,7 @@ +from django.conf import settings + +# The attriute on each test case that determines which apps to trace +APPS_TEST_CASE_ATTR = getattr(settings, 'APPS_TEST_CASE_ATTR', 'TESTS_APPS') + +# Modules not to trace +OMIT_MODULES = getattr(settings, 'OMIT_MODULES', ['*test*']) diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..df43693 --- /dev/null +++ b/setup.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +import os.path +import re +from setuptools import setup, find_packages + +def pkg_path(*components): + path = os.path.join(os.path.dirname(__file__), *components) + return os.path.realpath(path) + +def get_readme(): + with open(pkg_path('README.rst'), 'r') as readme: + return readme.read() + +def get_version(): + with open(pkg_path('discoverage', '__init__.py'), 'r') as init: + contents = init.read() + match = re.search(r'^__version__ = [\'"]([.\w]+)[\'"]', contents) + return match.group(1) + +setup( + name='django-discoverage', + version=get_version(), + author='Ryan Kaskel', + author_email='dev@ryankaskel.com', + url='https://github.com/ryankask/django-discoverage', + packages=find_packages(), + install_requires=['coverage>=3.5.3', 'django-discover-runner>=0.2.2'], + description='Jannis Leidel and Carl Meyer\'s django-discover-runner with coverage.', + long_description=get_readme(), + license='BSD', + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Environment :: Web Environment', + 'Framework :: Django', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Topic :: Software Development :: Testing', + ] +)