Passed
Push — feature/empty_name_assert ( 1516c1...ad4241 )
by srz
56:38
created

iutest_compile_error_test.ExpectMessage.__init__()   A

Complexity

Conditions 1

Size

Total Lines 4
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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