diff --git a/.gitignore b/.gitignore index 440e2c916..19aa34326 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,5 @@ build/ .emacs.desktop* *.egg-info/ dist/ +.coverage +plinth/tests/coverage/report/ diff --git a/HACKING b/HACKING index 1fdb7c79f..25d1e0aca 100644 --- a/HACKING +++ b/HACKING @@ -8,7 +8,7 @@ $ sudo python3 setup.py develop This will install the python package in a special development mode. Run it - normally. Any updates to the code (and core pakcage data files) do not + normally. Any updates to the code (and core package data files) do not require re-installation after every modification. CherryPy web server also monitors changes to the source files and reloads @@ -28,7 +28,7 @@ *Note:* This mode is supported only in a limited manner. The following are the unknown issues with it: - 1. Help pages are also not built. Run 'make -C doc' manaully. + 1. Help pages are also not built. Run 'make -C doc' manually. 2. Actions do not work when running as normal user without 'sudo' prefix. You need to add 'actions' directory to be allowed for 'sudo' commands. @@ -40,6 +40,24 @@ $ python3 setup.py test +## Running the Test Coverage Analysis + +1. Run the coverage tool: + + $ python3 setup.py test_coverage + + Invoking this command generates a binary-format '.coverage' data file in + the top-level project directory which is recreated with each run, and + writes a set of HTML and other supporting files which comprise the + browsable coverage report to the 'plinth/tests/coverage/report' directory. + Index.html presents the coverage summary, broken down by module. Data + columns can be sorted by clicking on the column header or by using mnemonic + hot-keys specified in the keyboard widget in the upper-right corner of the + page. Clicking on the name of a particular source file opens a page that + displays the contents of that file, with color-coding in the left margin to + indicate which statements or branches were executed via the tests (green) + and which statements or branches were not executed (red). + ## Testing Inside a Virtual Machine 1. Checkout source on the host. @@ -96,6 +114,8 @@ infrastructure in place for it from the start. Use it like this: * *CherryPy3* - WSGI web server since Django does not have proper web server +* *Coverage* - Test coverage measurement and reporting tool + * *Django* - Web application framework for Plinth * *JQuery* - Javascript framework used for convenience diff --git a/INSTALL b/INSTALL index fc3255e8d..80fe31ab0 100644 --- a/INSTALL +++ b/INSTALL @@ -6,7 +6,8 @@ $ sudo apt-get install libjs-jquery libjs-modernizr \ libjs-bootstrap make pandoc python3 python3-cherrypy3 \ - python3-django python3-bootstrapform python3-setuptools + python3-coverage python3-django python3-bootstrapform \ + python3-setuptools 2. Install Plinth: diff --git a/plinth/tests/coverage/test_coverage.py b/plinth/tests/coverage/test_coverage.py new file mode 100644 index 000000000..ec1aa654e --- /dev/null +++ b/plinth/tests/coverage/test_coverage.py @@ -0,0 +1,80 @@ +#!/usr/bin/python3 +# -*- mode: python; mode: auto-fill; fill-column: 80 -*- + +"""Support for integration of code test coverage analysis with setuptools. + +Derived from 'Adding Test Code Coverage Analysis to a Python Project’s Setup Command' + +""" + +import coverage +import glob +import setuptools +import shutil +import time +import unittest + +from plinth import tests + + +# project directories with testable source code +SOURCE_DIRS = ['plinth'] + glob.glob('plinth/modules/*') + +# files to exclude from coverage analysis and reporting +FILES_TO_OMIT = ['plinth/tests/*.py'] + +# location of coverage HTML report files +COVERAGE_REPORT_DIR = 'plinth/tests/coverage/report' + + +class TestCoverageCommand(setuptools.Command): + """ + Subclass of setuptools Command to perform code test coverage analysis. + """ + + description = "Run test coverage analysis" + user_options = [ + ('test-module=', 't', "Explicitly specify a single module to test") + ] + + def initialize_options(self): + """ + Initialize options to default values. + """ + self.test_module = None + + def finalize_options(self): + pass + + def run(self): + """ + Main command implementation. + """ + + # erase any existing HTML report files + try: + shutil.rmtree(COVERAGE_REPORT_DIR, True) + except: + pass + + # initialize a test suite for one or all modules + if self.test_module is None: + test_suite = tests.TEST_SUITE + else: + test = unittest.defaultTestLoader.loadTestsFromNames([self.test_module]) + test_suite = unittest.TestSuite(test) + + # run the coverage analysis + runner = unittest.TextTestRunner() + cov = coverage.coverage(auto_data=True, branch=True, source=SOURCE_DIRS, + omit=FILES_TO_OMIT) + cov.erase() # erase existing coverage data file + cov.start() + runner.run(test_suite) + cov.stop() + + # generate an HTML report + html_report_title = ("FreedomBox:Plinth -- Test Coverage as of " + + time.strftime("%x %X %Z")) + cov.html_report(directory=COVERAGE_REPORT_DIR, omit=FILES_TO_OMIT, + title=html_report_title) diff --git a/setup.py b/setup.py index e71b06273..aad799bb9 100755 --- a/setup.py +++ b/setup.py @@ -31,6 +31,7 @@ import shutil import subprocess from plinth import __version__ +from plinth.tests.coverage import test_coverage DIRECTORIES_TO_CREATE = [ @@ -113,7 +114,8 @@ setuptools.setup( install_requires=[ 'cherrypy >= 3.0', 'django >= 1.7.0', - 'django-bootstrap-form' + 'django-bootstrap-form', + 'coverage >= 3.7' ], include_package_data=True, package_data={'plinth': ['templates/*', @@ -134,6 +136,9 @@ setuptools.setup( ('/etc/plinth/modules-enabled', glob.glob(os.path.join('data/etc/plinth/modules-enabled', '*')))], - cmdclass={'clean': CustomClean, - 'install_data': CustomInstallData}, + cmdclass={ + 'clean': CustomClean, + 'install_data': CustomInstallData, + 'test_coverage': test_coverage.TestCoverageCommand + }, )