Completed
Push — develop ( d8b783...f586da )
by Jace
03:43
created

_launched_recently()   A

Complexity

Conditions 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1.512

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
dl 0
loc 5
ccs 1
cts 5
cp 0.2
crap 1.512
rs 9.4285
1
"""Plugins to extract coverage data from various formats."""
2
3 1
import os
4 1
from abc import ABCMeta, abstractmethod
5 1
import time
6 1
import webbrowser
7 1
import logging
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)):  # pragma: no cover (abstract class)
0 ignored issues
show
Complexity introduced by Jace Browning
This abstract class seems to be used only once.

Abstract classes which are used only once can usually be inlined into the class which already uses this abstract class.

Loading history...
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
30
    @abstractmethod
31
    def get_coverage(self, cwd):
32
        """Extract the coverage data from the current directory.
33
34
        :return float: Percentage of lines covered.
35
36
        """
37
38
    @abstractmethod
39
    def get_report(self, cwd):
40
        """Get the path to the coverage report.
41
42
        :return str: Path to coverage report or `None` if not available.
43
44
        """
45
46
47 1
def get_coverage(cwd=None):
48
    """Extract the current coverage data."""
49 1
    cwd = cwd or os.getcwd()
50
51 1
    plugin = _find_plugin(cwd)
52 1
    percentage = plugin.get_coverage(cwd)
53
54 1
    return round(percentage, 1)
55
56
57 1
def launch_report(cwd=None):
58
    """Open the generated coverage report in a web browser."""
59 1
    cwd = cwd or os.getcwd()
60
61 1
    plugin = _find_plugin(cwd, allow_missing=True)
62
63 1
    if plugin:
64
        path = plugin.get_report(cwd)
65
66
        if path:
67
            if _launched_recently(path):
68
                log.debug("Already launched: %s", path)
69
            else:
70
                log.info("Launching report: %s", path)
71
                webbrowser.open("file://" + path, new=2, autoraise=True)
72
73
74 1
def _find_plugin(cwd, allow_missing=False):
75
    """Find an return a matching coverage plugin."""
76 1
    for cls in BasePlugin.__subclasses__():  # pylint: disable=no-member
77 1
        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 1
    if allow_missing:
85 1
        return None
86
87
    raise RuntimeError(msg + '.')
88
89
90 1
def _launched_recently(path):
91
    now = time.time()
92
    then = cache.get(path, default=0)
93
    cache.set(path, now)
94
    return now - then > 60 * 60  # 1 hour
95
96
97 1
class CoveragePy(BasePlugin):
98
    """Coverage extractor for the coverage.py format."""
99
100 1
    def matches(self, cwd):
101 1
        return '.coverage' in os.listdir(cwd)
102
103 1
    def get_coverage(self, cwd):
104 1
        os.chdir(cwd)
105
106 1
        cov = coverage.Coverage()
0 ignored issues
show
Bug introduced by Jace Browning
The Module coverage does not seem to have a member named Coverage.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
107 1
        cov.load()
108
109 1
        with open(os.devnull, 'w') as ignore:
110 1
            total = cov.report(file=ignore)
111
112 1
        return total
113
114 1
    def get_report(self, cwd):
115
        path = os.path.join(cwd, 'htmlcov', 'index.html')
116
117
        if os.path.isfile(path):
118
            log.info("Found coverage report: %s", path)
119
            return path
120
121
        log.info("No coverage report found: %s", cwd)
122
        return None
123