Test Failed
Push — feature/empty_name_assert ( 8d8e26...8370b2 )
by srz
56:45
created

ErrorMessage.is_expansion()   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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