iutest_compile_error_test.iutest()   F
last analyzed

Complexity

Conditions 19

Size

Total Lines 45
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
eloc 39
nop 1
dl 0
loc 45
rs 0.5999
c 0
b 0
f 0

How to fix   Complexity   

Complexity

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