Completed
Push — master ( 889440...e965d2 )
by Dieter
01:16
created

buildtimetrend.travis.TravisSubstage   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 282
Duplicated Lines 0 %
Metric Value
dl 0
loc 282
rs 8.2608
wmc 40

15 Methods

Rating   Name   Duplication   Size   Complexity  
A TravisSubstage.__init__() 0 6 1
A TravisSubstage.has_command() 0 9 1
A TravisSubstage.get_command() 0 6 2
C TravisSubstage.process_parsed_tags() 0 23 7
A TravisSubstage.process_start_time() 0 22 3
B TravisSubstage.process_start_stage() 0 27 3
A TravisSubstage.has_timing_hash() 0 7 1
A TravisSubstage.has_finished() 0 15 1
B TravisSubstage.process_command() 0 23 4
C TravisSubstage.process_end_time() 0 53 7
A TravisSubstage.has_started() 0 7 1
B TravisSubstage.process_end_stage() 0 33 4
A TravisSubstage.get_name() 0 12 3
A TravisSubstage.set_name() 0 8 1
A TravisSubstage.has_name() 0 9 1

How to fix   Complexity   

Complex Class

Complex classes like buildtimetrend.travis.TravisSubstage 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
# vim: set expandtab sw=4 ts=4:
2
"""
3
Travis Substage class.
4
5
Copyright (C) 2014-2016 Dieter Adriaenssens <[email protected]>
6
7
This file is part of buildtimetrend/python-lib
8
<https://github.com/buildtimetrend/python-lib/>
9
10
This program is free software: you can redistribute it and/or modify
11
it under the terms of the GNU Affero General Public License as published by
12
the Free Software Foundation, either version 3 of the License, or
13
any later version.
14
15
This program is distributed in the hope that it will be useful,
16
but WITHOUT ANY WARRANTY; without even the implied warranty of
17
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
GNU Affero General Public License for more details.
19
20
You should have received a copy of the GNU Affero General Public License
21
along with this program. If not, see <http://www.gnu.org/licenses/>.
22
"""
23
from buildtimetrend import logger
24
from buildtimetrend.stages import Stage
25
from buildtimetrend.tools import check_dict
26
27
28
class TravisSubstage(object):
29
30
    """
31
    Travis CI substage object.
32
33
    It is constructed by feeding parsed tags from Travis CI logfile.
34
    """
35
36
    def __init__(self):
37
        """Initialise Travis CI Substage object."""
38
        self.stage = Stage()
39
        self.timing_hash = ""
40
        self.finished_incomplete = False
41
        self.finished = False
42
43
    def process_parsed_tags(self, tags_dict):
44
        """
45
        Process parsed tags and calls the corresponding handler method.
46
47
        Parameters:
48
        - tags_dict : dictionary with parsed tags
49
        """
50
        result = False
51
52
        # check if parameter tags_dict is a dictionary
53
        if check_dict(tags_dict, "tags_dict"):
54
            if 'start_stage' in tags_dict:
55
                result = self.process_start_stage(tags_dict)
56
            elif 'start_hash' in tags_dict:
57
                result = self.process_start_time(tags_dict)
58
            elif 'command' in tags_dict:
59
                result = self.process_command(tags_dict)
60
            elif 'end_hash' in tags_dict:
61
                result = self.process_end_time(tags_dict)
62
            elif 'end_stage' in tags_dict:
63
                result = self.process_end_stage(tags_dict)
64
65
        return result
66
67
    def process_start_stage(self, tags_dict):
68
        """
69
        Process parsed start_stage tags.
70
71
        Parameters:
72
        - tags_dict : dictionary with parsed tags
73
        """
74
        # check if parameter tags_dict is a dictionary and
75
        # if it contains all required tags
76
        tag_list = list({'start_stage', 'start_substage'})
77
        if not check_dict(tags_dict, "tags_dict", tag_list):
78
            return False
79
80
        logger.debug("Start stage : %s", tags_dict)
81
82
        result = False
83
84
        if self.has_started():
85
            logger.info("Substage already started")
86
        else:
87
            name = "{stage:s}.{substage:s}".format(
88
                stage=tags_dict['start_stage'],
89
                substage=tags_dict['start_substage']
90
            )
91
            result = self.set_name(name)
92
93
        return result
94
95
    def process_start_time(self, tags_dict):
96
        """
97
        Process parsed start_time tags.
98
99
        Parameters:
100
        - tags_dict : dictionary with parsed tags
101
        """
102
        # check if parameter tags_dict is a dictionary and
103
        # if it contains all required tags
104
        if not check_dict(tags_dict, "tags_dict", 'start_hash'):
105
            return False
106
107
        logger.debug("Start time : %s", tags_dict)
108
109
        if self.has_timing_hash():
110
            logger.info("Substage timing already set")
111
            return False
112
113
        self.timing_hash = tags_dict['start_hash']
114
        logger.info("Set timing hash : %s", self.timing_hash)
115
116
        return True
117
118
    def process_command(self, tags_dict):
119
        """
120
        Process parsed command tag.
121
122
        Parameters:
123
        - tags_dict : dictionary with parsed tags
124
        """
125
        # check if parameter tags_dict is a dictionary and
126
        # if it contains all required tags
127
        if not check_dict(tags_dict, "tags_dict", 'command'):
128
            return False
129
130
        logger.debug("Command : %s", tags_dict)
131
132
        result = False
133
134
        if self.has_command():
135
            logger.info("Command is already set")
136
        elif self.stage.set_command(tags_dict['command']):
137
            logger.info("Set command : %s", tags_dict['command'])
138
            result = True
139
140
        return result
141
142
    def process_end_time(self, tags_dict):
143
        """
144
        Process parsed end_time tags.
145
146
        Parameters:
147
        - tags_dict : dictionary with parsed tags
148
        """
149
        # check if parameter tags_dict is a dictionary and
150
        # if it contains all required tags
151
        tag_list = list({
152
            'end_hash',
153
            'start_timestamp',
154
            'finish_timestamp',
155
            'duration'
156
        })
157
        if not check_dict(tags_dict, "tags_dict", tag_list):
158
            return False
159
160
        logger.debug("End time : %s", tags_dict)
161
162
        result = False
163
164
        # check if timing was started
165
        # and if hash matches
166
        if (not self.has_timing_hash() or
167
                self.timing_hash != tags_dict['end_hash']):
168
            logger.info("Substage timing was not started or"
169
                        " hash doesn't match")
170
            self.finished_incomplete = True
171
        else:
172
            set_started = set_finished = set_duration = False
173
174
            # Set started timestamp
175
            if self.stage.set_started_at_nano(tags_dict['start_timestamp']):
176
                logger.info("Stage started at %s",
177
                            self.stage.data["started_at"]["isotimestamp"])
178
                set_started = True
179
180
            # Set finished timestamp
181
            if self.stage.set_finished_at_nano(tags_dict['finish_timestamp']):
182
                logger.info("Stage finished at %s",
183
                            self.stage.data["finished_at"]["isotimestamp"])
184
                set_finished = True
185
186
            # Set duration
187
            if self.stage.set_duration_nano(tags_dict['duration']):
188
                logger.info("Stage duration : %ss",
189
                            self.stage.data['duration'])
190
                set_duration = True
191
192
            result = set_started and set_finished and set_duration
193
194
        return result
195
196
    def process_end_stage(self, tags_dict):
197
        """
198
        Process parsed end_stage tags.
199
200
        Parameters:
201
        - tags_dict : dictionary with parsed tags
202
        """
203
        # check if parameter tags_dict is a dictionary and
204
        # if it contains all required tags
205
        tag_list = list({'end_stage', 'end_substage'})
206
        if not check_dict(tags_dict, "tags_dict", tag_list):
207
            return False
208
209
        logger.debug("End stage : %s", tags_dict)
210
211
        # construct substage name
212
        end_stagename = "{stage}.{substage}".format(
213
            stage=tags_dict['end_stage'],
214
            substage=tags_dict['end_substage']
215
        )
216
217
        # check if stage was started
218
        # and if substage name matches
219
        if not self.has_name() or self.stage.data["name"] != end_stagename:
220
            logger.info("Substage was not started or name doesn't match")
221
            self.finished_incomplete = True
222
            return False
223
224
        # stage finished successfully
225
        self.finished = True
226
        logger.info("Stage %s finished successfully", self.get_name())
227
228
        return True
229
230
    def get_name(self):
231
        """
232
        Return substage name.
233
234
        If name is not set, return the command.
235
        """
236
        if self.has_name():
237
            return self.stage.data["name"]
238
        elif self.has_command():
239
            return self.stage.data["command"]
240
        else:
241
            return ""
242
243
    def set_name(self, name):
244
        """
245
        Set substage name.
246
247
        Parameters:
248
        - name : substage name
249
        """
250
        return self.stage.set_name(name)
251
252
    def has_name(self):
253
        """
254
        Check if substage has a name.
255
256
        Returns true if substage has a name
257
        """
258
        return "name" in self.stage.data and \
259
            self.stage.data["name"] is not None and \
260
            len(self.stage.data["name"]) > 0
261
262
    def has_timing_hash(self):
263
        """
264
        Check if substage has a timing hash.
265
266
        Returns true if substage has a timing hash
267
        """
268
        return self.timing_hash is not None and len(self.timing_hash) > 0
269
270
    def has_command(self):
271
        """
272
        Check if a command is set for substage.
273
274
        Returns true if a command is set
275
        """
276
        return "command" in self.stage.data and \
277
            self.stage.data["command"] is not None and \
278
            len(self.stage.data["command"]) > 0
279
280
    def get_command(self):
281
        """Return substage command."""
282
        if self.has_command():
283
            return self.stage.data["command"]
284
        else:
285
            return ""
286
287
    def has_started(self):
288
        """
289
        Check if substage has started.
290
291
        Returns true if substage has started
292
        """
293
        return self.has_name() or self.has_timing_hash() or self.has_command()
294
295
    def has_finished(self):
296
        """
297
        Check if substage has finished.
298
299
        A substage is finished, if either the finished_timestamp is set,
300
        or if finished is (because of an error in parsing the tags).
301
302
        Returns true if substage has finished
303
        """
304
        return self.finished_incomplete or \
305
            self.has_name() and self.finished or \
306
            not self.has_name() and self.has_timing_hash() and \
307
            "finished_at" in self.stage.data or \
308
            not self.has_name() and not self.has_timing_hash() and \
309
            self.has_command()
310