Test Failed
Push — feature/zapcc_with_python ( 570545...c207d8 )
by srz
256:04 queued 187:55
created

iutest_compile_error_test.ErrorMessage.is_note()   A

Complexity

Conditions 2

Size

Total Lines 4
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nop 1
dl 0
loc 4
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
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
        '--debug',
206
        action='store_true',
207
        help='debug.'
208
    )
209
    parser.add_argument(
210
        '--command',
211
        help='execute command.',
212
        default=None
213
    )
214
    if sys.version_info[0] >= 3:
215
        parser.add_argument(
216
            '-i',
217
            '--infile',
218
            type=FileType('r', encoding='UTF-8'),
219
            help='compiler stdout.',
220
            default=sys.stdin
221
        )
222
    else:
223
        parser.add_argument(
224
            '-i',
225
            '--infile',
226
            type=FileType('r'),
227
            help='compiler stdout.',
228
            default=sys.stdin
229
        )
230
231
    options = parser.parse_args()
232
    return options
233
234
235
def parse_gcc_clang(options, f, r_expansion, note_is_child, root_is_expansion):
236
    re_fatal = re.compile(r'(\S+)\s*:\s*fatal\s*error\s*.*')
237
238
    class rmessage:
239
        re_file_col = re.compile(r'(\S+):(\d+):(\d+):(.*)')
240
        re_file = re.compile(r'(\S+):(\d+):(.*)')
241
        re_infile = re.compile(r'In file included from (\S+):(\d+):(?:\d+|)(.*)')
242
        re_ininst = re.compile(r'(\S+):\s* (In instantiation of .*)')
243
244
        def __init__(self):
245
            self.m0 = None
246
            self.m1 = None
247
            self.m2 = None
248
            self.m3 = None
249
250
        def match(self, line):
251
            self.m0 = self.re_file_col.match(line)
252
            if self.m0:
253
                return True
254
            self.m1 = self.re_file.match(line)
255
            if self.m1:
256
                return True
257
            self.m2 = self.re_infile.match(line)
258
            if self.m2:
259
                return True
260
            self.m3 = self.re_ininst.match(line)
261
            if self.m3:
262
                return True
263
            return False
264
265
        def file(self):
266
            if self.m0:
267
                return self.m0.group(1)
268
            if self.m1:
269
                return self.m1.group(1)
270
            if self.m2:
271
                return self.m2.group(1)
272
            if self.m3:
273
                return self.m3.group(1)
274
            return None
275
276
        def line(self):
277
            if self.m0:
278
                return int(self.m0.group(2))
279
            if self.m1:
280
                return int(self.m1.group(2))
281
            if self.m2:
282
                return int(self.m2.group(2))
283
            return 0
284
285
        def message(self):
286
            if self.m0:
287
                return self.m0.group(4)
288
            if self.m1:
289
                return self.m1.group(3)
290
            if self.m2:
291
                return self.m2.group(3)
292
            if self.m3:
293
                return self.m3.group(2)
294
            return None
295
296
        def is_included_from(self):
297
            if self.m2:
298
                return True
299
            return False
300
301
    re_message = re.compile(r'.*:\d+:\s*(\S*):\s*(.*)')
302
    re_expansion = re.compile(r_expansion)
303
    re_declaration = re.compile(r'.*declaration of\s*(.*)')
304
    re_required_from = re.compile(r'.*(required|instantiated) from here')
305
    re_provided_for = re.compile(r'\s*provided for .*')
306
    msg_list = []
307
    included_from_list = []
308
    msg = None
309
    prev = None
310
    is_prev_required_from = False
311
    for line in f:
312
        if options.passthrough:
313
            print(line.rstrip())
314
        if re_fatal.match(line):
315
            raise Exception(line)
316
317
        m = rmessage()
318
        if m.match(line):
319
            if msg:
320
                msg.included_from = copy.deepcopy(included_from_list)
321
                msg_list.append(msg)
322
                included_from_list[:] = []
323
                prev = msg
324
            if m.is_included_from():
325
                inc = ErrorMessage()
326
                inc.file = m.file()
327
                inc.line = m.line()
328
                inc.type = 'In file included from'
329
                included_from_list.append(inc)
330
                msg = None
331
                continue
332
            msg = ErrorMessage()
333
            msg.file = m.file()
334
            msg.line = m.line()
335
            msg.type = ""
336
            msg.root_is_expansion = root_is_expansion
337
            n = re_message.match(line)
338
            if n:
339
                msg.set_type(n.group(1))
340
                msg.message += n.group(2)
341
            else:
342
                msg.set_type('')
343
                msg.message += m.message()
344
345
            is_child = note_is_child and msg.is_note()
346
            is_type_none = prev and prev.is_type_none()
347
            is_declaration = False
348
            n = re_declaration.match(line)
349
            if n and prev and prev.message.find(n.group(1)) != -1:
350
                is_declaration = True
351
            if re_provided_for.match(msg.message):
352
                msg.provided_for = True
353
354
            if prev:
355
                is_expansion = re_expansion.search(msg.message)
356
                msg.expansion = is_expansion
357
                # print('%s - %d: %s %s %s %s' % (msg.file, msg.line, is_child, is_type_none, is_declaration, is_expansion))
358
                if is_child or is_type_none or is_declaration or is_expansion or is_prev_required_from:
359
                    prev.child = msg
360
                    msg.parent = prev
361
            is_prev_required_from = re_required_from.search(msg.message)
362
        else:
363
            if msg:
364
                msg.message += '\n'
365
                msg.message += line
366
    msg_list.append(msg)
367
    return msg_list
368
369
370
def parse_gcc(options, f):
371
    return parse_gcc_clang(options, f, r'in (definition|expansion) of macro', False, True)
372
373
374
def parse_clang(options, f):
375
    return parse_gcc_clang(options, f, r'expanded from ', True, False)
376
377
378
def parse_vc(options, f):
379
    re_fatal = re.compile(r'(\S+)\s*:\s*fatal\s*error\s*.*')
380
    re_file = re.compile(r'(\s*)(\S+)\((\d+)\)\s*:\s*(.*)')
381
    re_message = re.compile(r'.*\(\d+\)\s*: (\S*)\s*(\S*: .*)')
382
    msg_list = []
383
    msg = None
384
    prev = None
385
    for line in f:
386
        if options.passthrough:
387
            print(line.rstrip())
388
        if re_fatal.match(line):
389
            raise Exception(line)
390
391
        m = re_file.match(line)
392
        if m:
393
            if msg:
394
                msg_list.append(msg)
395
                prev = msg
396
            msg = ErrorMessage()
397
            msg.file = m.group(2)
398
            msg.line = int(m.group(3))
399
            msg.type = ""
400
            n = re_message.match(line)
401
            if n:
402
                msg.set_type(n.group(1))
403
                msg.message += n.group(2)
404
            else:
405
                msg.set_type('')
406
                msg.message += m.group(4)
407
408
            child_note = m.group(1) or (msg.is_note() and not msg.is_iutest())
409
            if (m.group(1) or child_note) and prev:
410
                prev.child = msg
411
                msg.parent = prev
412
        else:
413
            if msg:
414
                msg.message += '\n'
415
                msg.message += line
416
    msg_list.append(msg)
417
    return msg_list
418
419
420
def dump_included_from(m):
421
    for inc in m.included_from:
422
        print("%s %s:%d:" % (inc.type, inc.file, inc.line))
423
424
425
def dump_msg(m):
426
    dump_included_from(m)
427
    s = m.file
428
    s += ':'
429
    if format_gcc:
430
        if m.line:
431
            s += '%d:' % (m.line)
432
        if not m.is_type_none():
433
            s += ' %s:' % (m.type)
434
        print("%s %s" % (s, m.message))
435
    else:
436
        if m.line:
437
            s += '(%d):' % (m.line)
438
        if m.parent:
439
            print("\t%s %s %s" % (s, m.type, m.message))
440
        else:
441
            print("%s %s %s" % (s, m.type, m.message))
442
443
444
def dump_msgs(m):
445
    if m.parent:
446
        dump_msgs(m.parent)
447
    dump_msg(m)
448
449
450
def dump_list(l):
451
    for m in l:
452
        if not m.parent:
453
            print('------------------------------')
454
        dump_msg(m)
455
        print('==')
456
        if not m.child:
457
            print('------------------------------')
458
459
    return True
460
461
462
def test_result(result, msg, e):
463
    OKGREEN = '\033[32m'
464
#   WARNING = '\033[33m'
465
    FAIL = '\033[31m'
466
    ENDC = '\033[0m'
467
468
    if e:
469
        msg += ': ' + e.file + ': ' + str(e.line)
470
471
    if result:
472
        if color_prompt:
473
            print(OKGREEN + '[OK] ' + ENDC + msg)
474
        else:
475
            print('[OK] ' + msg)
476
    else:
477
        if color_prompt:
478
            print(FAIL + '[NG] ' + ENDC + msg)
479
        else:
480
            print('[NG] ' + msg)
481
482
483
def iutest(l):
484
    result = True
485
    unexpected = []
486
    re_iutest = re.compile(r'IUTEST_TEST_COMPILEERROR\( ([^#]*) \)')
487
    checkList = []
488
    messageList = []
489
    for i, msg in enumerate(l):
490
        if not msg:
491
            continue
492
        mm = re_iutest.search(msg.message)
493
        if mm:
494
            root = msg.get_source_msg()
495
            if not root.target:
496
                expect = mm.group(1).strip('"')
497
                checkList.append(ExpectMessage(root, expect, mm.group(0)))
498
                root.target = True
499
        else:
500
            messageList.append(msg)
501
502
    for msg in messageList:
503
        if msg.has_error():
504
            #print('%s - %d' % (msg.file, msg.line))
505
            for check in checkList:
506
                if msg.file in check.msg.file and msg.line == check.msg.line + 1:
507
                    actual = msg.get_error()
508
                    #print(actual.message)
509
                    msg.checked = True
510
                    if not check.expect or re.search(check.expect, actual.message):
511
                        check.msg.checked = True
512
                        break
513
            if msg.is_tail() and not msg.is_checked() and not msg.is_provided_for():
514
                unexpected.append(msg)
515
        elif msg.is_warning():
516
            dump_msg(msg)
517
518
    for check in checkList:
519
        if check.msg.checked:
520
            test_result(True, check.expression, check.msg)
521
        else:
522
            test_result(False, check.expression, check.msg)
523
            result = False
524
    for msg in unexpected:
525
        test_result(False, "Unexpected error: " + msg.message, msg)
526
        result = False
527
    return result
528
529
530
def parse_output(options):
531
    global format_gcc
532
    l = None
533
    if options.command:
534
        args = re.split('\s+', options.command)
535
        for i in range(len(args)):
536
            args[i] = os.path.expandvars(args[i])
537
        p = Popen(args, stdout=PIPE, stderr=STDOUT)
538
        p.wait()
539
        out, err = p.communicate()
540
        f = out.decode('utf-8').splitlines()
541
    else:
542
        if not options.infile:
543
            raise Exception("infile null")
544
        #print(options.infile.encoding)
545
        f = options.infile
546
        #f = codecs.getreader('utf-8')(options.infile)
547
548
    if any(options.compiler.find(s) != -1 for s in ('clang', 'clang++', 'zapcc', 'zapcc++')):
549
        l = parse_clang(options, f)
550
    elif any(options.compiler.find(s) != -1 for s in ('gcc', 'g++')):
551
        l = parse_gcc(options, f)
552
    elif options.compiler == 'cl':
553
        format_gcc = False
554
        l = parse_vc(options, f)
555
    else:
556
        raise Exception("sorry, %s compiler is not supported", (options.compiler))
557
558
    if options.debug:
559
        dump_list(l)
560
    if options.passthrough:
561
        print('----')
562
    result = iutest(l)
563
    if not result:
564
        if options.verbose:
565
            dump_list(l)
566
    return result
567
568
def setup():
569
    global color_prompt
570
    term = os.environ.get('TERM')
571
    if term:
572
        if any(term.find(s) for s in ('xterm', 'screen', 'rxvt', 'linux', 'cygwin')):
573
            color_prompt = True
574
575
576
def main():
577
    try:
578
        options = parse_command_line()
579
        setup()
580
        if not parse_output(options):
581
            sys.exit(1)
582
    except Exception as e:
583
        print(e)
584
        raise
585
586
if __name__ == '__main__':
587
    main()
588