Passed
Push — develop ( e76d2d...088138 )
by Jace
01:20
created

launch_report()   A

Complexity

Conditions 4

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
c 1
b 0
f 0
dl 0
loc 12
ccs 4
cts 8
cp 0.5
crap 6
rs 9.2
1
"""Plugins to extract coverage data from various formats."""
2
3 1
import logging
4 1
import os
5 1
import time
6 1
import webbrowser
7 1
from abc import ABCMeta, abstractmethod
8
9 1
import coverage
10 1
from six import with_metaclass
11
12 1
from .cache import Cache
13
14
15 1
log = logging.getLogger(__name__)
16 1
cache = Cache()
17
18
19
class BasePlugin(with_metaclass(ABCMeta)):  # pylint: disable=no-init
20
    """Base class for coverage plugins."""
21
22
    @abstractmethod
23
    def matches(self, cwd):
24
        """Determine if the current directory contains coverage data.
25
26
        :return bool: Indicates that the current directory should be processed.
27
28
        """
29
        raise NotImplementedError
30
31
    @abstractmethod
32
    def get_coverage(self, cwd):
33
        """Extract the coverage data from the current directory.
34
35
        :return float: Percentage of lines covered.
36
37
        """
38
        raise NotImplementedError
39
40
    @abstractmethod
41
    def get_report(self, cwd):
42
        """Get the path to the coverage report.
43
44
        :return str: Path to coverage report or `None` if not available.
45
46
        """
47 1
        raise NotImplementedError
48
49 1
50
def get_coverage(cwd=None):
51 1
    """Extract the current coverage data."""
52 1
    cwd = cwd or os.getcwd()
53
54 1
    plugin = _find_plugin(cwd)
55
    percentage = plugin.get_coverage(cwd)
56
57 1
    return round(percentage, 1)
58
59 1
60
def launch_report(cwd=None):
61 1
    """Open the generated coverage report in a web browser."""
62
    cwd = cwd or os.getcwd()
63 1
64
    plugin = _find_plugin(cwd, allow_missing=True)
65
66
    if plugin:
67
        path = plugin.get_report(cwd)
68
69
        if path and not _launched_recently(path):
70
            log.info("Launching report: %s", path)
71 1
            webbrowser.open("file://" + path, new=2, autoraise=True)
72
73 1
74 1
def _find_plugin(cwd, allow_missing=False):
75 1
    """Find an return a matching coverage plugin."""
76 1
    for cls in BasePlugin.__subclasses__():  # pylint: disable=no-member
77
        plugin = cls()
78 1
        if plugin.matches(cwd):
79 1
            return plugin
80
81 1
    msg = "No coverage data found: {}".format(cwd)
82 1
    log.info(msg)
83
84
    if allow_missing:
85
        return None
86
87 1
    raise RuntimeError(msg)
88 1
89 1
90 1
def _launched_recently(path):
91 1
    now = time.time()
92 1
    then = cache.get(path, default=0)
93 1
    elapsed = now - then
94
    log.debug("Last launched %s seconds ago", elapsed)
95
    cache.set(path, now)
96 1
    return elapsed < 60 * 60  # 1 hour
97
98
99 1
class CoveragePy(BasePlugin):  # pylint: disable=no-init
100 1
    """Coverage extractor for the coverage.py format."""
101
102 1
    def matches(self, cwd):
103 1
        return any((
104
            '.coverage' in os.listdir(cwd),
105 1
            '.coveragerc' in os.listdir(cwd),
106 1
        ))
107
108 1
    def get_coverage(self, cwd):
109 1
        os.chdir(cwd)
110
111 1
        cov = coverage.Coverage()
112
        cov.load()
113 1
114
        with open(os.devnull, 'w') as ignore:
115
            total = cov.report(file=ignore)
116
117
        return total
118
119
    def get_report(self, cwd):
120
        path = os.path.join(cwd, 'htmlcov', 'index.html')
121
122
        if os.path.isfile(path):
123
            log.info("Found coverage report: %s", path)
124
            return path
125
126
        log.info("No coverage report found: %s", cwd)
127
        return None
128