Completed
Push — master ( d5fdbe...1b7e63 )
by srz
08:59 queued 04:24
created

iutest_compile_error_test   F

Complexity

Total Complexity 142

Size/Duplication

Total Lines 497
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 392
dl 0
loc 497
rs 1.5789
c 0
b 0
f 0
wmc 142

13 Functions

Rating   Name   Duplication   Size   Complexity  
A dump_list() 0 9 4
F iutest() 0 40 17
B test_result() 0 19 5
A parse_gcc() 0 2 1
F parse_vc() 0 40 11
A dump_msgs() 0 4 2
F parse_gcc_clang() 0 96 27
A setup() 0 6 4
A parse_command_line() 0 48 2
A parse_clang() 0 2 1
F parse_output() 0 33 11
B dump_msg() 0 16 6
A main() 0 9 3

15 Methods

Rating   Name   Duplication   Size   Complexity  
B ErrorMessage.set_type() 0 16 7
B ErrorMessage.get_error() 0 12 6
B ErrorMessage.has_error() 0 8 6
A ErrorMessage.get_error_child() 0 6 3
A ErrorMessage.has_error_parent() 0 6 3
A ErrorMessage.is_warning() 0 4 2
A ErrorMessage.is_error() 0 4 2
A ExpectMessage.__init__() 0 4 1
A ErrorMessage.is_note() 0 4 2
A ErrorMessage.is_tail() 0 4 2
A ErrorMessage.is_checked() 0 6 3
A ErrorMessage.is_type_none() 0 4 2
A ErrorMessage.is_iutest() 0 4 3
A ErrorMessage.has_error_child() 0 6 3
A ErrorMessage.get_error_parent() 0 6 3

How to fix   Complexity   

Complexity

Complex classes like iutest_compile_error_test 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
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
#
4
# iutest_compiler_error_test.py
5
#
6
# Copyright (C) 2015-2016, Takazumi Shirayanagi
7
# This software is released under the new BSD License,
8
# see LICENSE
9
#
10
11
import os
12
import sys
13
import argparse
14
import re
15
16
from argparse import ArgumentParser
17
from subprocess import Popen, PIPE, STDOUT
18
19
20
class ErrorMessage:
21
    file = ""
22
    line = 0
23
    type = ""
24
    message = ""
25
    parent = None
26
    child = None
27
    checked = False
28
29
    def set_type(self, str):
30
        s = str.strip()
31
        if s in {"error", u"エラー"}:
32
            self.type = "error"
33
        elif s in {"note", u"備考"}:
34
            self.type = "note"
35
        elif s in {"warning", u"警告"}:
36
            self.type = "warning"
37
        if s in {"error", "エラー"}:
38
            self.type = "error"
39
        elif s in {"note", "備考"}:
40
            self.type = "note"
41
        elif s in {"warning", "警告"}:
42
            self.type = "warning"
43
        else:
44
            self.type = s
45
46
    def is_error(self):
47
        if self.type == "error":
48
            return True
49
        return False
50
51
    def is_note(self):
52
        if self.type == "note":
53
            return True
54
        return False
55
56
    def is_iutest(self):
57
        if self.is_note() and self.message.find("IUTEST_TEST_COMPILEERROR") != -1:
58
            return True
59
        return False
60
61
    def is_type_none(self):
62
        if not self.type:
63
            return True
64
        return False
65
66
    def is_warning(self):
67
        if self.type == "warning":
68
            return True
69
        return False
70
71
    def has_error(self):
72
        if self.type == "error":
73
            return True
74
        elif self.parent and self.parent.has_error_parent():
75
            return True
76
        elif self.child and self.child.has_error_child():
77
            return True
78
        return False
79
80
    def has_error_parent(self):
81
        if self.type == "error":
82
            return True
83
        elif self.parent:
84
            return self.parent.has_error_parent()
85
        return False
86
87
    def has_error_child(self):
88
        if self.type == "error":
89
            return True
90
        elif self.child:
91
            return self.child.has_error_child()
92
        return False
93
94
    def is_checked(self):
95
        if self.checked:
96
            return True
97
        elif self.parent:
98
            return self.parent.is_checked()
99
        return False
100
101
    def is_tail(self):
102
        if self.child:
103
            return False
104
        return True
105
106
    def get_error(self):
107
        if self.type == "error":
108
            return self
109
        if self.parent:
110
            e = self.parent.get_error_parent()
111
            if e:
112
                return e
113
        if self.child:
114
            e = self.child.get_error_child()
115
            if e:
116
                return e
117
        return None
118
119
    def get_error_parent(self):
120
        if self.type == "error":
121
            return self
122
        elif self.parent:
123
            return self.parent.get_error_parent()
124
        return None
125
126
    def get_error_child(self):
127
        if self.type == "error":
128
            return self
129
        elif self.child:
130
            return self.child.get_error_child()
131
        return None
132
133
134
class ExpectMessage:
135
    def __init__(self, msg, expect, expression):
136
        self.msg = msg
137
        self.expect = expect
138
        self.expression = expression
139
140
141
format_gcc = True
142
color_prompt = False
143
144
145
# command line option
146
def parse_command_line():
147
    parser = ArgumentParser()
148
    parser.add_argument(
149
        '-v',
150
        '--version',
151
        action='version',
152
        version=u'%(prog)s version 0.4'
153
    )
154
    parser.add_argument(
155
        '-c',
156
        '--compiler',
157
        help='set compiler.',
158
        default='gcc'
159
    )
160
    parser.add_argument(
161
        '--verbose',
162
        action='store_true',
163
        help='print input message.'
164
    )
165
    parser.add_argument(
166
        '--debug',
167
        action='store_true',
168
        help='debug.'
169
    )
170
    parser.add_argument(
171
        '--command',
172
        help='execute command.',
173
        default=None
174
    )
175
    if sys.version_info[0] >= 3:
176
        parser.add_argument(
177
            '-i',
178
            '--infile',
179
            type=argparse.FileType('r', encoding='UTF-8'),
180
            help='compiler stdout.',
181
            default=sys.stdin
182
        )
183
    else:
184
        parser.add_argument(
185
            '-i',
186
            '--infile',
187
            type=argparse.FileType('r'),
188
            help='compiler stdout.',
189
            default=sys.stdin
190
        )
191
192
    options = parser.parse_args()
193
    return options
194
195
196
def parse_gcc_clang(options, f, r_expansion, note_is_child):
197
    re_fatal = re.compile(r'(\S+)\s*:\s*fatal\s*error\s*.*')
198
199
    class rmessage:
200
        re_file = re.compile(r'(\S+):(\d+):(?:\d+\s*:|)(.*)')
201
        re_infile = re.compile(r'In file included from (\S+):(\d+):(?:\d+|)(.*)')
202
        re_ininst = re.compile(r'(\S+):\s* (In instantiation of .*)')
203
204
        def __init__(self):
205
            self.m1 = None
206
            self.m2 = None
207
            self.m3 = None
208
209
        def match(self, line):
210
            self.m1 = self.re_file.match(line)
211
            if self.m1:
212
                return True
213
            self.m2 = self.re_infile.match(line)
214
            if self.m2:
215
                return True
216
            self.m3 = self.re_ininst.match(line)
217
            if self.m3:
218
                return True
219
            return False
220
221
        def file(self):
222
            if self.m1:
223
                return self.m1.group(1)
224
            if self.m2:
225
                return self.m2.group(1)
226
            if self.m3:
227
                return self.m3.group(1)
228
            return None
229
230
        def line(self):
231
            if self.m1:
232
                return int(self.m1.group(2))
233
            if self.m2:
234
                return int(self.m2.group(2))
235
            return None
236
237
        def message(self):
238
            if self.m1:
239
                return self.m1.group(3)
240
            if self.m2:
241
                return self.m2.group(3)
242
            if self.m3:
243
                return self.m3.group(2)
244
            return None
245
246
    re_message = re.compile(r'.*:\d+:(\d+:|)\s*(\S*):\s*(.*)')
247
    re_expansion = re.compile(r_expansion)
248
    re_declaration = re.compile(r'.*declaration of\s*(.*)')
249
    msg_list = []
250
    msg = None
251
    prev = None
252
    for line in f:
253
        if options.verbose:
254
            print(line.rstrip())
255
        if re_fatal.match(line):
256
            raise Exception(line)
257
258
        m = rmessage()
259
        if m.match(line):
260
            if msg:
261
                msg_list.append(msg)
262
                prev = msg
263
            msg = ErrorMessage()
264
            msg.file = m.file()
265
            msg.line = m.line()
266
            msg.type = ""
267
            n = re_message.match(line)
268
            if n:
269
                msg.set_type(n.group(1))
270
                msg.message += n.group(2)
271
            else:
272
                msg.set_type('')
273
                msg.message += m.message()
274
275
            is_child = note_is_child and msg.is_note()
276
            is_type_none = prev and prev.is_type_none()
277
            is_declaration = False
278
            n = re_declaration.match(line)
279
            if n and prev and prev.message.find(n.group(1)) != -1:
280
                is_declaration = True
281
282
            if prev:
283
                if is_child or is_type_none or is_declaration or re_expansion.search(msg.message):
284
                    prev.child = msg
285
                    msg.parent = prev
286
        else:
287
            if msg:
288
                msg.message += '\n'
289
                msg.message += line
290
    msg_list.append(msg)
291
    return msg_list
292
293
294
def parse_gcc(options, f):
295
    return parse_gcc_clang(options, f, r'in expansion of macro', False)
296
297
298
def parse_clang(options, f):
299
    return parse_gcc_clang(options, f, r'expanded from ', True)
300
301
302
def parse_vc(options, f):
303
    re_fatal = re.compile(r'(\S+)\s*:\s*fatal\s*error\s*.*')
304
    re_file = re.compile(r'(\s*)(\S+)\((\d+)\)\s*:\s*(.*)')
305
    re_message = re.compile(r'.*\(\d+\)\s*: (\S*)\s*(\S*: .*)')
306
    msg_list = []
307
    msg = None
308
    prev = None
309
    for line in f:
310
        if options.verbose:
311
            print(line.rstrip())
312
        if re_fatal.match(line):
313
            raise Exception(line)
314
315
        m = re_file.match(line)
316
        if m:
317
            if msg:
318
                msg_list.append(msg)
319
                prev = msg
320
            msg = ErrorMessage()
321
            msg.file = m.group(2)
322
            msg.line = int(m.group(3))
323
            msg.type = ""
324
            n = re_message.match(line)
325
            if n:
326
                msg.set_type(n.group(1))
327
                msg.message += n.group(2)
328
            else:
329
                msg.set_type('')
330
                msg.message += m.group(4)
331
332
            child_note = m.group(1) or (msg.is_note() and not msg.is_iutest())
333
            if (m.group(1) or child_note) and prev:
334
                prev.child = msg
335
                msg.parent = prev
336
        else:
337
            if msg:
338
                msg.message += '\n'
339
                msg.message += line
340
    msg_list.append(msg)
341
    return msg_list
342
343
344
def dump_msg(m):
345
    s = m.file
346
    s += ':'
347
    if format_gcc:
348
        if m.line:
349
            s += '%d:' % (m.line)
350
        if not m.is_type_none():
351
            s += ' %s:' % (m.type)
352
        print("%s %s" % (s, m.message))
353
    else:
354
        if m.line:
355
            s += '(%d);' % (m.line)
356
        if m.parent:
357
            print("\t%s %s %s" % (s, m.type, m.message))
358
        else:
359
            print("%s %s %s" % (s, m.type, m.message))
360
361
362
def dump_msgs(m):
363
    if m.parent:
364
        dump_msgs(m.parent)
365
    dump_msg(m)
366
367
368
def dump_list(l):
369
    for m in l:
370
        if not m.parent:
371
            print('------------------------------')
372
        dump_msg(m)
373
        if not m.child:
374
            print('------------------------------')
375
376
    return True
377
378
379
def test_result(result, msg, e):
380
    OKGREEN = '\033[32m'
381
#   WARNING = '\033[33m'
382
    FAIL = '\033[31m'
383
    ENDC = '\033[0m'
384
385
    if e:
386
        msg += ': ' + e.file + ': ' + str(e.line)
387
388
    if result:
389
        if color_prompt:
390
            print(OKGREEN + '[OK] ' + ENDC + msg)
391
        else:
392
            print('[OK] ' + msg)
393
    else:
394
        if color_prompt:
395
            print(FAIL + '[NG] ' + ENDC + msg)
396
        else:
397
            print('[NG] ' + msg)
398
399
400
def iutest(l):
401
    result = True
402
    re_iutest = re.compile(r'IUTEST_TEST_COMPILEERROR\( (.*) \)')
403
    checkList = []
404
    messageList = []
405
    for i, msg in enumerate(l):
406
        if not msg:
407
            continue
408
        mm = re_iutest.search(msg.message)
409
        if mm:
410
            if not msg.parent:
411
                expect = mm.group(1).strip('"')
412
                checkList.append(ExpectMessage(msg, expect, mm.group(0)))
413
        else:
414
            messageList.append(msg)
415
416
    for msg in messageList:
417
        if msg.has_error():
418
            #print('%s - %d' % (msg.file, msg.line))
419
            for check in checkList:
420
                if msg.file in check.msg.file and msg.line == check.msg.line + 1:
421
                    actual = msg.get_error()
422
                    #print(actual.message)
423
                    msg.checked = True
424
                    if not check.expect or actual.message.find(check.expect) != -1:
425
                        check.msg.checked = True
426
                        break
427
            if msg.is_tail() and not msg.is_checked():
428
                dump_msgs(msg)
429
                result = False
430
        elif msg.is_warning():
431
            dump_msg(msg)
432
433
    for check in checkList:
434
        if check.msg.checked:
435
            test_result(True, check.expression, check.msg)
436
        else:
437
            test_result(False, check.expression, check.msg)
438
            result = False
439
    return result
440
441
442
def parse_output(options):
443
    global format_gcc
444
    l = None
445
    if options.command:
446
        args = re.split('\s+', options.command)
447
        for i in range(len(args)):
448
            args[i] = os.path.expandvars(args[i])
449
        p = Popen(args, stdout=PIPE, stderr=STDOUT)
450
        p.wait()
451
        out, err = p.communicate()
452
        f = out.decode('utf-8').splitlines()
453
    else:
454
        if not options.infile:
455
            raise Exception("infile null")
456
        #print(options.infile.encoding)
457
        f = options.infile
458
        #f = codecs.getreader('utf-8')(options.infile)
459
460
    if any(options.compiler.find(s) != -1 for s in ('clang', 'clang++')):
461
        l = parse_clang(options, f)
462
    elif any(options.compiler.find(s) != -1 for s in ('gcc', 'g++')):
463
        l = parse_gcc(options, f)
464
    elif options.compiler == 'cl':
465
        format_gcc = False
466
        l = parse_vc(options, f)
467
    else:
468
        raise Exception("sorry, %s compiler is not supported", (options.compiler))
469
470
    if options.debug:
471
        dump_list(l)
472
    if options.verbose:
473
        print('----')
474
    return iutest(l)
475
476
477
def setup():
478
    global color_prompt
479
    term = os.environ.get('TERM')
480
    if term:
481
        if any(term.find(s) for s in ('xterm', 'screen', 'rxvt', 'linux', 'cygwin')):
482
            color_prompt = True
483
484
485
def main():
486
    try:
487
        options = parse_command_line()
488
        setup()
489
        if not parse_output(options):
490
            sys.exit(1)
491
    except Exception as e:
492
        print(e)
493
        raise
494
495
if __name__ == '__main__':
496
    main()
497