xref: /openbmc/qemu/tests/qapi-schema/test-qapi.py (revision 41d0ad1d)
1c88ee46cSPhilippe Mathieu-Daudé#!/usr/bin/env python3
298626572SMarkus Armbruster#
398626572SMarkus Armbruster# QAPI parser test harness
498626572SMarkus Armbruster#
598626572SMarkus Armbruster# Copyright (c) 2013 Red Hat Inc.
698626572SMarkus Armbruster#
798626572SMarkus Armbruster# Authors:
898626572SMarkus Armbruster#  Markus Armbruster <armbru@redhat.com>
998626572SMarkus Armbruster#
1098626572SMarkus Armbruster# This work is licensed under the terms of the GNU GPL, version 2 or later.
1198626572SMarkus Armbruster# See the COPYING file in the top-level directory.
1298626572SMarkus Armbruster#
1398626572SMarkus Armbruster
14e6c42b96SMarkus Armbruster
15f01338ccSMarkus Armbrusterimport argparse
16f01338ccSMarkus Armbrusterimport difflib
17f01338ccSMarkus Armbrusterimport os
1898626572SMarkus Armbrusterimport sys
19ed39c03eSMarkus Armbrusterfrom io import StringIO
20e6c42b96SMarkus Armbruster
21e6c42b96SMarkus Armbrusterfrom qapi.error import QAPIError
22e6c42b96SMarkus Armbrusterfrom qapi.schema import QAPISchema, QAPISchemaVisitor
23e6c42b96SMarkus Armbruster
2498626572SMarkus Armbruster
25156402e5SMarkus Armbrusterclass QAPISchemaTestVisitor(QAPISchemaVisitor):
26cf40a0a5SMarkus Armbruster
27cf40a0a5SMarkus Armbruster    def visit_module(self, name):
28cf40a0a5SMarkus Armbruster        print('module %s' % name)
29cf40a0a5SMarkus Armbruster
30cf40a0a5SMarkus Armbruster    def visit_include(self, name, info):
31cf40a0a5SMarkus Armbruster        print('include %s' % name)
32cf40a0a5SMarkus Armbruster
33013b4efcSMarkus Armbruster    def visit_enum_type(self, name, info, ifcond, features, members, prefix):
341e381b65SMarc-André Lureau        print('enum %s' % name)
35156402e5SMarkus Armbruster        if prefix:
36ef9d9108SDaniel P. Berrange            print('    prefix %s' % prefix)
371e381b65SMarc-André Lureau        for m in members:
381e381b65SMarc-André Lureau            print('    member %s' % m.name)
396cc32b0eSMarc-André Lureau            self._print_if(m.ifcond, indent=8)
40b6c18755SMarkus Armbruster            self._print_features(m.features, indent=8)
41fbf09a2fSMarc-André Lureau        self._print_if(ifcond)
42013b4efcSMarkus Armbruster        self._print_features(features)
43156402e5SMarkus Armbruster
44ca0ac758SMarkus Armbruster    def visit_array_type(self, name, info, ifcond, element_type):
45ca0ac758SMarkus Armbruster        if not info:
46ca0ac758SMarkus Armbruster            return              # suppress built-in arrays
47ca0ac758SMarkus Armbruster        print('array %s %s' % (name, element_type.name))
48ca0ac758SMarkus Armbruster        self._print_if(ifcond)
49ca0ac758SMarkus Armbruster
507b3bc9e2SMarkus Armbruster    def visit_object_type(self, name, info, ifcond, features,
51d1da8af8SMarkus Armbruster                          base, members, branches):
52ef9d9108SDaniel P. Berrange        print('object %s' % name)
53156402e5SMarkus Armbruster        if base:
54ef9d9108SDaniel P. Berrange            print('    base %s' % base.name)
55156402e5SMarkus Armbruster        for m in members:
56b736e25aSMarkus Armbruster            print('    member %s: %s optional=%s'
57b736e25aSMarkus Armbruster                  % (m.name, m.type.name, m.optional))
58ccadd6bcSMarc-André Lureau            self._print_if(m.ifcond, 8)
5984ab0086SMarkus Armbruster            self._print_features(m.features, indent=8)
60d1da8af8SMarkus Armbruster        self._print_variants(branches)
61fbf09a2fSMarc-André Lureau        self._print_if(ifcond)
622e2e0df2SPeter Krempa        self._print_features(features)
63156402e5SMarkus Armbruster
64*41d0ad1dSMarkus Armbruster    def visit_alternate_type(self, name, info, ifcond, features,
65*41d0ad1dSMarkus Armbruster                             alternatives):
66ef9d9108SDaniel P. Berrange        print('alternate %s' % name)
67*41d0ad1dSMarkus Armbruster        self._print_variants(alternatives)
68fbf09a2fSMarc-André Lureau        self._print_if(ifcond)
69013b4efcSMarkus Armbruster        self._print_features(features)
70156402e5SMarkus Armbruster
717b3bc9e2SMarkus Armbruster    def visit_command(self, name, info, ifcond, features,
727b3bc9e2SMarkus Armbruster                      arg_type, ret_type, gen, success_response, boxed,
7304f22362SKevin Wolf                      allow_oob, allow_preconfig, coroutine):
74b736e25aSMarkus Armbruster        print('command %s %s -> %s'
75b736e25aSMarkus Armbruster              % (name, arg_type and arg_type.name,
76b736e25aSMarkus Armbruster                 ret_type and ret_type.name))
7704f22362SKevin Wolf        print('    gen=%s success_response=%s boxed=%s oob=%s preconfig=%s%s'
7804f22362SKevin Wolf              % (gen, success_response, boxed, allow_oob, allow_preconfig,
7904f22362SKevin Wolf                 " coroutine=True" if coroutine else ""))
80fbf09a2fSMarc-André Lureau        self._print_if(ifcond)
812e2e0df2SPeter Krempa        self._print_features(features)
82156402e5SMarkus Armbruster
83013b4efcSMarkus Armbruster    def visit_event(self, name, info, ifcond, features, arg_type, boxed):
84ef9d9108SDaniel P. Berrange        print('event %s %s' % (name, arg_type and arg_type.name))
85ef9d9108SDaniel P. Berrange        print('    boxed=%s' % boxed)
86fbf09a2fSMarc-André Lureau        self._print_if(ifcond)
87013b4efcSMarkus Armbruster        self._print_features(features)
88156402e5SMarkus Armbruster
89156402e5SMarkus Armbruster    @staticmethod
90156402e5SMarkus Armbruster    def _print_variants(variants):
91156402e5SMarkus Armbruster        if variants:
92ef9d9108SDaniel P. Berrange            print('    tag %s' % variants.tag_member.name)
93156402e5SMarkus Armbruster            for v in variants.variants:
94ef9d9108SDaniel P. Berrange                print('    case %s: %s' % (v.name, v.type.name))
95a2724280SMarc-André Lureau                QAPISchemaTestVisitor._print_if(v.ifcond, indent=8)
96156402e5SMarkus Armbruster
97fbf09a2fSMarc-André Lureau    @staticmethod
98fbf09a2fSMarc-André Lureau    def _print_if(ifcond, indent=4):
999c629fa8SMarkus Armbruster        # TODO Drop this hack after replacing OrderedDict by plain
1009c629fa8SMarkus Armbruster        # dict (requires Python 3.7)
1019c629fa8SMarkus Armbruster        def _massage(subcond):
1029c629fa8SMarkus Armbruster            if isinstance(subcond, str):
1039c629fa8SMarkus Armbruster                return subcond
1049c629fa8SMarkus Armbruster            if isinstance(subcond, list):
1059c629fa8SMarkus Armbruster                return [_massage(val) for val in subcond]
1069c629fa8SMarkus Armbruster            return {key: _massage(val) for key, val in subcond.items()}
1079c629fa8SMarkus Armbruster
10833aa3267SMarc-André Lureau        if ifcond.is_present():
1099c629fa8SMarkus Armbruster            print('%sif %s' % (' ' * indent, _massage(ifcond.ifcond)))
110fbf09a2fSMarc-André Lureau
1112e2e0df2SPeter Krempa    @classmethod
11284ab0086SMarkus Armbruster    def _print_features(cls, features, indent=4):
1132e2e0df2SPeter Krempa        if features:
1142e2e0df2SPeter Krempa            for f in features:
11584ab0086SMarkus Armbruster                print('%sfeature %s' % (' ' * indent, f.name))
11684ab0086SMarkus Armbruster                cls._print_if(f.ifcond, indent + 4)
1172e2e0df2SPeter Krempa
118181feaf3SMarkus Armbruster
119f01338ccSMarkus Armbrusterdef test_frontend(fname):
120f01338ccSMarkus Armbruster    schema = QAPISchema(fname)
121156402e5SMarkus Armbruster    schema.visit(QAPISchemaTestVisitor())
122818c3318SMarkus Armbruster
123818c3318SMarkus Armbruster    for doc in schema.docs:
124818c3318SMarkus Armbruster        if doc.symbol:
125ef9d9108SDaniel P. Berrange            print('doc symbol=%s' % doc.symbol)
126818c3318SMarkus Armbruster        else:
127ef9d9108SDaniel P. Berrange            print('doc freeform')
128ef9d9108SDaniel P. Berrange        print('    body=\n%s' % doc.body.text)
1292f848044SDaniel P. Berrange        for arg, section in doc.args.items():
130ef9d9108SDaniel P. Berrange            print('    arg=%s\n%s' % (arg, section.text))
131a0418a4aSMarkus Armbruster        for feat, section in doc.features.items():
132a0418a4aSMarkus Armbruster            print('    feature=%s\n%s' % (feat, section.text))
133818c3318SMarkus Armbruster        for section in doc.sections:
13431c54b92SMarkus Armbruster            print('    section=%s\n%s' % (section.tag, section.text))
135f01338ccSMarkus Armbruster
136f01338ccSMarkus Armbruster
137f333681cSMarkus Armbrusterdef open_test_result(dir_name, file_name, update):
138f333681cSMarkus Armbruster    mode = 'r+' if update else 'r'
139f333681cSMarkus Armbruster    try:
1405c24c3e2SMarkus Armbruster        return open(os.path.join(dir_name, file_name), mode, encoding='utf-8')
141f333681cSMarkus Armbruster    except FileNotFoundError:
142f333681cSMarkus Armbruster        if not update:
143f333681cSMarkus Armbruster            raise
1445c24c3e2SMarkus Armbruster    return open(os.path.join(dir_name, file_name), 'w+', encoding='utf-8')
145f333681cSMarkus Armbruster
146f333681cSMarkus Armbruster
147f01338ccSMarkus Armbrusterdef test_and_diff(test_name, dir_name, update):
148f01338ccSMarkus Armbruster    sys.stdout = StringIO()
149f01338ccSMarkus Armbruster    try:
150f01338ccSMarkus Armbruster        test_frontend(os.path.join(dir_name, test_name + '.json'))
151f01338ccSMarkus Armbruster    except QAPIError as err:
152f01338ccSMarkus Armbruster        errstr = str(err) + '\n'
153f01338ccSMarkus Armbruster        if dir_name:
154f01338ccSMarkus Armbruster            errstr = errstr.replace(dir_name + '/', '')
155f01338ccSMarkus Armbruster        actual_err = errstr.splitlines(True)
156f01338ccSMarkus Armbruster    else:
157f01338ccSMarkus Armbruster        actual_err = []
158f01338ccSMarkus Armbruster    finally:
159f01338ccSMarkus Armbruster        actual_out = sys.stdout.getvalue().splitlines(True)
160f01338ccSMarkus Armbruster        sys.stdout.close()
161f01338ccSMarkus Armbruster        sys.stdout = sys.__stdout__
162f01338ccSMarkus Armbruster
163f01338ccSMarkus Armbruster    try:
164f333681cSMarkus Armbruster        outfp = open_test_result(dir_name, test_name + '.out', update)
165f333681cSMarkus Armbruster        errfp = open_test_result(dir_name, test_name + '.err', update)
166f01338ccSMarkus Armbruster        expected_out = outfp.readlines()
167f01338ccSMarkus Armbruster        expected_err = errfp.readlines()
168436911c2SMarkus Armbruster    except OSError as err:
169f01338ccSMarkus Armbruster        print("%s: can't open '%s': %s"
170f01338ccSMarkus Armbruster              % (sys.argv[0], err.filename, err.strerror),
171f01338ccSMarkus Armbruster              file=sys.stderr)
172f01338ccSMarkus Armbruster        return 2
173f01338ccSMarkus Armbruster
174f01338ccSMarkus Armbruster    if actual_out == expected_out and actual_err == expected_err:
175f01338ccSMarkus Armbruster        return 0
176f01338ccSMarkus Armbruster
177f01338ccSMarkus Armbruster    print("%s %s" % (test_name, 'UPDATE' if update else 'FAIL'),
178f01338ccSMarkus Armbruster          file=sys.stderr)
179f01338ccSMarkus Armbruster    out_diff = difflib.unified_diff(expected_out, actual_out, outfp.name)
180f01338ccSMarkus Armbruster    err_diff = difflib.unified_diff(expected_err, actual_err, errfp.name)
181f01338ccSMarkus Armbruster    sys.stdout.writelines(out_diff)
182f01338ccSMarkus Armbruster    sys.stdout.writelines(err_diff)
183f01338ccSMarkus Armbruster
184f01338ccSMarkus Armbruster    if not update:
185f01338ccSMarkus Armbruster        return 1
186f01338ccSMarkus Armbruster
187f01338ccSMarkus Armbruster    try:
188f01338ccSMarkus Armbruster        outfp.truncate(0)
189f01338ccSMarkus Armbruster        outfp.seek(0)
190f01338ccSMarkus Armbruster        outfp.writelines(actual_out)
191f01338ccSMarkus Armbruster        errfp.truncate(0)
192f01338ccSMarkus Armbruster        errfp.seek(0)
193f01338ccSMarkus Armbruster        errfp.writelines(actual_err)
194436911c2SMarkus Armbruster    except OSError as err:
195f01338ccSMarkus Armbruster        print("%s: can't write '%s': %s"
196f01338ccSMarkus Armbruster              % (sys.argv[0], err.filename, err.strerror),
197f01338ccSMarkus Armbruster              file=sys.stderr)
198f01338ccSMarkus Armbruster        return 2
199f01338ccSMarkus Armbruster
200f01338ccSMarkus Armbruster    return 0
201f01338ccSMarkus Armbruster
202f01338ccSMarkus Armbruster
203f01338ccSMarkus Armbrusterdef main(argv):
204f01338ccSMarkus Armbruster    parser = argparse.ArgumentParser(
205f01338ccSMarkus Armbruster        description='QAPI schema tester')
206f01338ccSMarkus Armbruster    parser.add_argument('-d', '--dir', action='store', default='',
207f01338ccSMarkus Armbruster                        help="directory containing tests")
208f01338ccSMarkus Armbruster    parser.add_argument('-u', '--update', action='store_true',
2097ce54db2SDaniel P. Berrangé                        default='QAPI_TEST_UPDATE' in os.environ,
210f01338ccSMarkus Armbruster                        help="update expected test results")
211f01338ccSMarkus Armbruster    parser.add_argument('tests', nargs='*', metavar='TEST', action='store')
212f01338ccSMarkus Armbruster    args = parser.parse_args()
213f01338ccSMarkus Armbruster
214f01338ccSMarkus Armbruster    status = 0
215f01338ccSMarkus Armbruster    for t in args.tests:
216f01338ccSMarkus Armbruster        (dir_name, base_name) = os.path.split(t)
217f01338ccSMarkus Armbruster        dir_name = dir_name or args.dir
218f01338ccSMarkus Armbruster        test_name = os.path.splitext(base_name)[0]
219f01338ccSMarkus Armbruster        status |= test_and_diff(test_name, dir_name, args.update)
220f01338ccSMarkus Armbruster
2215c24c3e2SMarkus Armbruster    sys.exit(status)
222f01338ccSMarkus Armbruster
223f01338ccSMarkus Armbruster
224f01338ccSMarkus Armbrusterif __name__ == '__main__':
225f01338ccSMarkus Armbruster    main(sys.argv)
2265c24c3e2SMarkus Armbruster    sys.exit(0)
227