Source

django-coverage / django_coverage / html_test_runner.py

Full commit
"""Running tests"""

import sys
import time
import os
import cgi
import traceback
#import unittest
from jinja2 import Environment, PackageLoader

from django.utils.unittest.runner import TextTestRunner
from django.utils.unittest.runner import TextTestResult
from utils.result_processor import ResultProcessor, CoverageProcessor, Summary, TestInfo, TestGroup

from utils.coverage_report.data_storage import ModuleVars

try:
    from django.utils.unittest.signals import registerResult
except ImportError:
    def registerResult(_):
        pass

__unittest = True


class SourceLine():
  def __init__(self, line, status):
    self.line = line.decode('utf-8')
#    print line
    self.status = status

class ModuleCoverage():
  def __init__(self, mVars):
    self.css = mVars.severity
    self.name = mVars.module_name
    self.total_lines = mVars.total_count
    self.executed_lines = mVars.executed_count
    self.excluded_lines = mVars.excluded_count
    self.missed_lines = mVars.missed_count
    self.ignored_lines = 0
    self.coverage_percent = "%.2f" % mVars.percent_covered
    self.url = 'modules/%s.html' % self.name

    self.source_lines = list()

    source_lines = list()
    i = 0
    for i, source_line in enumerate(
        [cgi.escape(l.rstrip()) for l in file(mVars.source_file, 'rb').readlines()]):
        line_status = 'ignored'
        if i+1 in mVars.executed: line_status = 'executed'
        if i+1 in mVars.excluded: line_status = 'excluded'
        if i+1 in mVars.missed: line_status = 'missed'
        self.source_lines.append( SourceLine( source_line, line_status ) )
    self.ignored_count = i+1 - mVars.total_count


class HTMLTestResult(TextTestResult):
    all_tests = dict()

    def __init__(self, stream, descriptions, verbusity):
        TextTestResult.__init__( self, stream, descriptions, verbusity )

    def appendTest(self, test, type_class, data = ''):
        info = TestInfo( test = test, result_class = type_class, data = data )
        if not info.group in self.all_tests:
          self.all_tests[info.group] = TestGroup( name = info.group )
        self.all_tests[info.group].appendTestInfo( info )

    def addSuccess(self, test):
        TextTestResult.addSuccess( self, test )
        self.appendTest( test, 'success' )

    def addError(self, test, err):
        TextTestResult.addError( self, test, err )
        self.appendTest( test, 'error', "".join( traceback.format_exception( err[0], err[1], err[2] ) ) )

    def addFailure(self, test, err):
        TextTestResult.addFailure( self, test, err )
        self.appendTest( test, 'failure', "".join( traceback.format_exception( err[0], err[1], err[2] ) ) )

    def addSkip(self, test, reason):
        TextTestResult.addSkip( self, test, reason )
        self.appendTest( test, 'skip', reason )

    def addExpectedFalure(self, test, err):
        TextTestResult.addExpectedFailure( self, test, err)
        self.appendTest( test, 'expectedfailure', "".join( traceback.format_exception( err[0], err[1], err[2] ) ) )

    def addUnexpectedSuccess(self, test):
        TextTestResult.addUnexpectedSuccess( self, test )
        self.appendTest( test, 'unexpectedsuccess' )


class HTMLTestRunner(TextTestRunner):
    """A test runner class that displays results in HTML form.

    """
    def __init__(self, stream=sys.stderr, descriptions=True, verbosity=1,
                    failfast=False, buffer=False):
        TextTestRunner.__init__( self, stream, descriptions, verbosity, failfast, buffer, HTMLTestResult )
        self.reportPath = None
        self.result = None

    def run(self, test):
        "Run the given test case or test suite."
        result = self._makeResult()
        result.failfast = self.failfast
        result.buffer = self.buffer
        registerResult(result)

        startTime = time.time()
        startTestRun = getattr(result, 'startTestRun', None)
        if startTestRun is not None:
            startTestRun()
        try:
            test(result)
        finally:
            stopTestRun = getattr(result, 'stopTestRun', None)
            if stopTestRun is not None:
                stopTestRun()
            else:
                result.printErrors()
        stopTime = time.time()
        timeTaken = stopTime - startTime
        if hasattr(result, 'separator2'):
            self.stream.writeln(result.separator2)
        run = result.testsRun
        self.stream.writeln("Ran %d test%s in %.3fs" %
                            (run, run != 1 and "s" or "", timeTaken))
        self.stream.writeln()
        result.timeTaken = "%.3f s" % timeTaken
        stat = Summary( result )
        infos = []
        if not result.wasSuccessful():
            self.stream.write("FAILED")
            if stat.fails:
                infos.append("failures=%d" % stat.fails)
            if stat.errors:
              infos.append("errors=%d" % stat.errors)
        else:
            self.stream.write("OK")
        if stat.skips:
            infos.append("skipped=%d" % stat.skips)
        if stat.expected_fails:
            infos.append("expected failures=%d" % stat.expected_fails)
        if stat.unexpected_successes:
            infos.append("unexpected successes=%d" % stat.unexpected_successes)
        if infos:
            self.stream.writeln(" (%s)" % (", ".join(infos),))
        else:
            self.stream.write("\n")

        self.result = result
        return result

    def setReportPath(self, aPath):
        self.reportPath = aPath

    def generateReport(self, coverage_results):
        if self.result is None or self.reportPath is None:
            return False

        coverage_path = os.path.join( self.reportPath, 'coverage' )
        try:
          os.mkdir( coverage_path )
        except OSError:
          pass
        coverage_modules_path = os.path.join( coverage_path, 'modules' )
        try:
          os.mkdir( coverage_modules_path )
        except OSError:
          pass
        env = Environment( loader = PackageLoader( 'django_coverage', 'templates' ) )
        
        modules_summary = { 'total_lines': 0
                          , 'executed_lines': 0
                          , 'excluded_lines': 0 
                          , 'coverage_percent': 0
                          , 'css': 'normal'
                          }
        total_stmts = 0

        if coverage_results['modules']:
        
          modules = list()
          m_names = coverage_results['modules'].keys()
          m_names.sort()
          
          for cur_name in m_names:
            mVars = ModuleVars( cur_name, coverage_results['modules'][cur_name] )
            if not mVars.total_count:
              coverage_results['excludes'].append( cur_name )
              continue
            modules_summary['total_lines'] += mVars.total_count
            modules_summary['executed_lines'] += mVars.executed_count
            modules_summary['excluded_lines'] += mVars.excluded_count
            total_stmts += len(mVars.stmts)

            moduleInfo = ModuleCoverage( mVars )
            t = env.get_template( 'coverage_module.html' )
            t.stream( { 'module': moduleInfo, } ).dump( os.path.join( coverage_modules_path, cur_name + '.html' ), encoding = 'utf-8' )
            
            modules.append( ModuleCoverage( mVars ) )

          try:
            modules_summary['coverage_percent'] = float( modules_summary['executed_lines'] ) / total_stmts * 100
          except ZeroDivisionError:
            modules_summary['coverage_percent'] = 100

          if modules_summary['coverage_percent'] < 75:
            modules_summary['css'] = 'warning'
          if modules_summary['coverage_percent'] < 50:
            modules_summary['css'] = 'critical'
            
          modules_summary['coverage_percent'] = "%.2f" % modules_summary['coverage_percent']
          
          t = env.get_template( 'coverage.html' )
          t.stream( { 'modules': modules
                    , 'summary': modules_summary } ).dump( os.path.join( coverage_path, 'index.html' ), encoding = 'utf-8' )

        if coverage_results['errors']:
          t = env.get_template( 'coverage_errors.html' )
          t.stream( { 'list': coverage_results['errors'] } ).dump( os.path.join( coverage_path, 'errors.html' ), encoding = 'utf-8' )

        if coverage_results['excludes']:
          t = env.get_template( 'coverage_excludes.html' )
          t.stream( { 'list': coverage_results['excludes'] } ).dump( os.path.join( coverage_path, 'excludes.html' ), encoding = 'utf-8' )

        t = env.get_template( 'index.html' )
        t.stream( ResultProcessor( self.result, modules_summary ).context() ).dump( os.path.join( self.reportPath, 'index.html' ), encoding = 'utf-8' )

        return True