From f12883003a6d040ab2679329e31407fc7307496d Mon Sep 17 00:00:00 2001
From: Ryan Kaskel <dev@ryankaskel.com>
Date: Wed, 24 Oct 2012 15:21:34 +0100
Subject: [PATCH] Initial commit.

---
 .gitignore              |  8 +++++++
 LICENSE                 | 25 ++++++++++++++++++++++
 MANIFEST.in             |  2 ++
 README.rst              | 47 +++++++++++++++++++++++++++++++++++++++++
 discoverage/__init__.py |  3 +++
 discoverage/runner.py   | 33 +++++++++++++++++++++++++++++
 discoverage/settings.py |  7 ++++++
 setup.py                | 43 +++++++++++++++++++++++++++++++++++++
 8 files changed, 168 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 LICENSE
 create mode 100644 MANIFEST.in
 create mode 100644 README.rst
 create mode 100644 discoverage/__init__.py
 create mode 100644 discoverage/runner.py
 create mode 100644 discoverage/settings.py
 create mode 100755 setup.py

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 <http://nedbatchelder.com/code/coverage/>`_ to Jannis Leidel and
+Carl Meyer's `django-discover-runner
+<https://github.com/jezdez/django-discover-runner>`_.
+
+Inspired by `django-coverage <https://bitbucket.org/kmike/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
+  <http://nedbatchelder.com/code/coverage/api.html#coverage.coverage>`_ 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',
+    ]
+)