1import os
2import sys
3import difflib
4import builtins
5
6path = os.getenv('UNINSTALLED_INTROSPECTION_SRCDIR', None)
7assert path is not None
8sys.path.insert(0, path)
9
10# Not correct, but enough to get the tests going uninstalled
11builtins.__dict__['DATADIR'] = path
12builtins.__dict__['GIRDIR'] = ''
13
14from giscanner.annotationparser import GtkDocCommentBlockParser
15from giscanner.ast import Include, Namespace
16from giscanner.introspectablepass import IntrospectablePass
17from giscanner.maintransformer import MainTransformer
18from giscanner.message import MessageLogger
19from giscanner.sourcescanner import SourceScanner
20from giscanner.transformer import Transformer
21from giscanner.scannermain import process_packages
22
23
24currentdir = os.path.dirname(os.path.abspath(sys.argv[0]))
25current_name = os.path.basename(currentdir)
26path = os.path.abspath(os.path.join(currentdir, '..', ''))
27top_srcdir = os.environ['UNINSTALLED_INTROSPECTION_SRCDIR']
28top_builddir = os.environ['TOP_BUILDDIR']
29
30
31class ChunkedIO(object):
32    def __init__(self):
33        self.buffer = []
34
35    def write(self, s):
36        self.buffer.append(s)
37
38    def getvalue(self):
39        return self.buffer
40
41
42class Options:
43    def __init__(self):
44        self.cpp_includes = []
45        self.cpp_defines = []
46        self.cpp_undefines = []
47        self.library_paths = []
48
49
50def _diff(a, b):
51    retval = ''
52    started = False
53
54    for group in difflib.SequenceMatcher(None, a, b).get_grouped_opcodes(3):
55        if not started:
56            started = True
57            retval += '--- expected\n'
58            retval += '+++ emitted\n'
59
60        for tag, i1, i2, j1, j2 in group:
61            if tag == 'equal':
62                for line in a[i1:i2]:
63                    for l in line.split('\n'):
64                        retval += ' ' + l + '\n'
65                continue
66
67            if tag in ('replace', 'delete'):
68                for line in a[i1:i2]:
69                    for l in line.split('\n'):
70                        retval += '-' + l + '\n'
71
72            if tag in ('replace', 'insert'):
73                for line in b[j1:j2]:
74                    for l in line.split('\n'):
75                        retval += '+' + l + '\n'
76
77    return retval
78
79
80def _extract_expected(filename):
81    fd = open(filename, 'r', encoding='utf-8')
82    data = fd.read()
83    fd.close()
84
85    retval = []
86    for line in data.split('\n'):
87        if line.startswith('// EXPECT:'):
88            retval.append(line[10:] + '\n')
89        elif line.startswith('//+'):
90            retval[-1] += line[3:] + '\n'
91
92    return retval
93
94
95def check(args):
96    filename = args[0]
97
98    output = ChunkedIO()
99    namespace = Namespace('Test', '1.0')
100    logger = MessageLogger.get(namespace=namespace, output=output)
101    logger.enable_warnings(True)
102
103    transformer = Transformer(namespace)
104    transformer.set_include_paths([
105        os.path.join(top_srcdir, 'gir'),
106        top_builddir,
107        os.path.join(top_builddir, 'gir'),
108    ])
109    transformer.register_include(Include.from_string('GObject-2.0'))
110
111    ss = SourceScanner()
112
113    options = Options()
114    exit_code = process_packages(options, ['gobject-2.0'])
115    if exit_code:
116        sys.exit(exit_code)
117    ss.set_cpp_options(options.cpp_includes, options.cpp_defines, options.cpp_undefines)
118    ss.parse_files([filename])
119    ss.parse_macros([filename])
120    transformer.parse(ss.get_symbols())
121
122    cbp = GtkDocCommentBlockParser()
123    blocks = cbp.parse_comment_blocks(ss.get_comments())
124
125    main = MainTransformer(transformer, blocks)
126    main.transform()
127
128    final = IntrospectablePass(transformer, blocks)
129    final.validate()
130
131    emitted_warnings = [w[w.find(':') + 1:] for w in output.getvalue()]
132
133    expected_warnings = _extract_expected(filename)
134
135    sortkey = lambda x: int(x.split(':')[0])
136    expected_warnings.sort(key=sortkey)
137    emitted_warnings.sort(key=sortkey)
138
139    if len(expected_warnings) != len(emitted_warnings):
140        raise SystemExit("ERROR in '%s': %d warnings were emitted, "
141                         "expected %d:\n%s" % (os.path.basename(filename),
142                                               len(emitted_warnings),
143                                               len(expected_warnings),
144                                               _diff(expected_warnings, emitted_warnings)))
145
146    for emitted_warning, expected_warning in zip(emitted_warnings, expected_warnings):
147        if expected_warning != emitted_warning:
148            raise SystemExit("ERROR in '%s': expected warning does not match emitted "
149                             "warning:\n%s" % (filename,
150                                               _diff([expected_warning], [emitted_warning])))
151
152
153sys.exit(check(sys.argv[1:]))
154