GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 074cc7...987304 )
by Gonzalo
59s
created

Tool.make_config_dictionary()   F

Complexity

Conditions 11

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
dl 0
loc 30
rs 3.1764
c 0
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like Tool.make_config_dictionary() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# -*- coding: utf-8 -*-
2
# -----------------------------------------------------------------------------
3
# Copyright (c) 2016 Continuum Analytics, Inc.
4
#
5
# Licensed under the terms of the MIT License
6
# (see LICENSE.txt for details)
7
# -----------------------------------------------------------------------------
8
"""Generic tools and custom test runner."""
9
10
# Standard library imports
11
from collections import OrderedDict
12
import ast
13
import json
14
import os
15
16
# Third party imports
17
from pytest_cov.plugin import CoverageError
18
from six import PY2
19
from six.moves import configparser
20
import pytest
21
22
# Local imports
23
from ciocheck.config import COVERAGE_CONFIGURATION_FILE
24
from ciocheck.utils import ShortOutput, cpu_count
25
26
27
class Tool(object):
28
    """Generic tool object."""
29
30
    name = None
31
    language = None
32
    extensions = None
33
34
    command = None
35
36
    # Config
37
    config_file = None  # '.validconfigfilename'
38
    config_sections = None  # (('ciocheck:section', 'section'))
39
40
    def __init__(self, cmd_root):
41
        """A Generic tool object."""
42
        self.cmd_root = cmd_root
43
        self.config = None
44
        self.config_options = None  # dict version of the config
45
46
    def create_config(self, config):
47
        """Create a config file for for a given config fname and sections."""
48
        self.config = config
49
50
        if self.config_file and self.config_sections:
51
            new_config = configparser.ConfigParser()
52
            new_config_file = os.path.join(self.cmd_root, self.config_file)
53
54
            for (cio_config_section, config_section) in self.config_sections:
55
                if config.has_section(cio_config_section):
56
                    items = config.items(cio_config_section)
57
                    new_config.add_section(config_section)
58
59
                    for option, value in items:
60
                        new_config.set(config_section, option, value)
61
62
            with open(new_config_file, 'w') as file_obj:
63
                new_config.write(file_obj)
64
65
    @classmethod
66
    def make_config_dictionary(cls):
67
        """Turn config into a dictionary for later usage."""
68
        config_path = os.path.join(cls.cmd_root, cls.config_file)
69
        config_options = {}
70
71
        if os.path.exists(config_path):
72
            config = configparser.ConfigParser()
73
74
            with open(config_path, 'r') as file_obj:
75
                config.readfp(file_obj)
76
77
            for section in config.sections():
78
                for key in config[section]:
79
                    value = config[section][key]
80
                    if ',' in value:
81
                        value = [v for v in value.split(',') if v]
82
                    elif value.lower() == 'false':
83
                        value = False
84
                    elif value.lower() == 'true':
85
                        value = True
86
                    else:
87
                        try:
88
                            value = ast.literal_eval(value)  # Numbers
89
                        except Exception as err:
90
                            print(err)
91
92
                    config_options[key.replace('-', '_')] = value
93
94
        return config_options
95
96
    @classmethod
97
    def remove_config(cls, path):
98
        """Remove config file."""
99
        if cls.config_file and cls.config_sections:
100
            remove_file = os.path.join(path, cls.config_file)
101
            if os.path.isfile(remove_file):
102
                os.remove(remove_file)
103
104
    def run(self, paths):
105
        """Run the tool."""
106
        raise NotImplementedError
107
108
109
class CoverageTool(Tool):
110
    """Coverage tool runner."""
111
112
    name = 'coverage'
113
    language = 'python'
114
    extensions = ('py', )
115
116
    # Config
117
    config_file = COVERAGE_CONFIGURATION_FILE
118
    config_sections = [
119
        ('coverage:run', 'run'),
120
        ('coverage:report', 'report'),
121
        ('coverage:html', 'html'),
122
        ('coverage:xml', 'xml'),
123
    ]
124
125
    def _monkey_path_coverage(self):
126
        """Enforce the value of `skip_covered`, ignored by pytest-cov.
127
128
        pytest-cov ignores the option even if included in the .coveragerc
129
        configuration file.
130
        """
131
#        try:
132
#            original_init = coverage.summary.SummaryReporter.__init__
133
#
134
#            def modified_init(self, coverage, config):
135
#                config.skip_covered = True
136
#                original_init(self, coverage, config)
137
#
138
#            coverage.summary.SummaryReporter.__init__ = modified_init
139
#
140
#            print("\nCoverage monkeypatched to skip_covered")
141
#        except Exception as e:
142
#            print("\nFailed to monkeypatch coverage: {0}".format(str(e)),
143
#                  file=sys.stderr)
144
145
    def run(self, paths):
146
        """Run the tool."""
147
        return []
148
149
    @classmethod
150
    def remove_config(cls, path):
151
        """Remove config file."""
152
        pass
153
154
155
class PytestTool(Tool):
156
    """Pytest tool runner."""
157
158
    name = 'pytest'
159
    language = 'python'
160
    extensions = ('py', )
161
162
    config_file = 'pytest.ini'
163
    config_sections = [('pytest', 'pytest')]
164
165
    REPORT_FILE = '.pytestreport.json'
166
167
    def __init__(self, cmd_root):
168
        """Pytest tool runner."""
169
        super(PytestTool, self).__init__(cmd_root)
170
        self.pytest_args = None
171
        self.output = None
172
        self.coverage_fail = False
173
174
    def setup_pytest_coverage_args(self, paths):
175
        """Setup pytest-cov arguments and config file path."""
176
        if isinstance(paths, (dict, OrderedDict)):
177
            paths = list(sorted(paths.keys()))
178
179
        for path in paths:
180
            if os.path.isdir(path):
181
                cov = '--cov={0}'.format(path)
182
                coverage_args = [cov]
183
                break
184
        else:
185
            coverage_args = []
186
187
        coverage_config_file = os.path.join(self.cmd_root,
188
                                            COVERAGE_CONFIGURATION_FILE)
189
        if os.path.isfile(coverage_config_file):
190
            cov_config = ['--cov-config', coverage_config_file]
191
            coverage_args = cov_config + coverage_args
192
193
        if PY2:
194
            # xdist appears to lock up the test suite with python2, maybe due
195
            # to an interaction with coverage
196
            enable_xdist = []
197
        else:
198
            enable_xdist = ['-n', str(cpu_count())]
199
200
        self.pytest_args = ['--json={0}'.format(self.REPORT_FILE)]
201
        self.pytest_args = self.pytest_args + enable_xdist
202
        self.pytest_args = self.pytest_args + coverage_args
203
204
    def run(self, paths):
205
        """Run pytest test suite."""
206
        cmd = paths + self.pytest_args
207
208
        try:
209
            with ShortOutput(self.cmd_root) as so:
210
                errno = pytest.main(cmd)
211
            output_lines = ''.join(so.output).lower()
212
213
            if 'FAIL Required test coverage'.lower() in output_lines:
214
                self.coverage_fail = True
215
216
            if errno != 0:
217
                print("pytest failed, code {errno}".format(errno=errno))
218
        except CoverageError as e:
219
            print("Test coverage failure: " + str(e))
220
            self.coverage_fail = True
221
222
        covered_lines = self.parse_coverage()
223
        pytest_report = self.parse_pytest_report()
224
225
        results = {'coverage': covered_lines}
226
        if pytest_report is not None:
227
            results['pytest'] = pytest_report
228
        return results
229
230
    def parse_pytest_report(self):
231
        """Parse pytest json resport generated by pytest-json."""
232
        data = None
233
        pytest_report_path = os.path.join(self.cmd_root, self.REPORT_FILE)
234
        if os.path.isfile(pytest_report_path):
235
            with open(pytest_report_path, 'r') as file_obj:
236
                data = json.load(file_obj)
237
        return data
238
239
    def parse_coverage(self):
240
        """Parse .coverage json report generated by coverage."""
241
        coverage_string = ("!coverage.py: This is a private format, don't "
242
                           "read it directly!")
243
        coverage_path = os.path.join(self.cmd_root, '.coverage')
244
245
        covered_lines = {}
246
        if os.path.isfile(coverage_path):
247
            with open(coverage_path, 'r') as file_obj:
248
                data = file_obj.read()
249
                data = data.replace(coverage_string, '')
250
251
            cov = json.loads(data)
252
            covered_lines = OrderedDict()
253
            lines = cov['lines']
254
            for path in sorted(lines):
255
                covered_lines[path] = lines[path]
256
        return covered_lines
257
258
    @classmethod
259
    def remove_config(cls, path):
260
        """Remove config file."""
261
        super(PytestTool, cls).remove_config(path)
262
        remove_file = os.path.join(path, cls.REPORT_FILE)
263
        if os.path.isfile(remove_file):
264
            os.remove(remove_file)
265
266
267
TOOLS = [
268
    CoverageTool,
269
    PytestTool,
270
]
271
272
273
def test():
274
    """Main local test."""
275
    pass
276
277
278
if __name__ == '__main__':
279
    test()
280